/**
 * 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.
 */
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

(function(window, Object, undefined) {
    "use strict";
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */
 
// 如果已经定义过qc.VERSION变量，则代表js重复加载执行，直接return不再重复定义
if (window.qc && window.qc.Node) {
    return;
}

/**
 * 深拷贝
 * @param object 将要被深拷贝的对象
 */
var deepCopy = function(object) {
    var copied = [];

    var contains = function(arr, obj) {
        for (var i in arr)
        {
            if (arr[i] == obj)
                return true;
        }

        return false;
    };

    var copyImp = function(obj) {
        if (typeof(obj) !== 'object' || obj === null)
            return obj;
        else if (contains(copied, obj))
            return obj;

        copied.push(obj);

        var newObject = {};
        if (obj.constructor === Array) {
            newObject = [];
        }

        for (var i in obj) {
            // 过滤从上级原形继承的属性
            if (! obj.hasOwnProperty(i))
                continue;

            newObject[i] = copyImp(obj[i]);
        }

        return newObject;
    };

    return copyImp(object);
};

/**
 * 混合注入属性
 * @param object 将要被注入属性的对象
 * @param properties 需要注入的属性
 * @param keepExist 是否保留已经定义过的属性，默认为false
 */
var mixin = function(object, properties, keepExist) {
    for (var name in properties) {
        if (!keepExist || object[name] === undefined) {
            object[name] = properties[name];
        }
    }
    return object;
};

if (!window.qc){
    window.qc = {};
}
var qc = window.qc,
    Phaser = window.Phaser,
    PIXI = window.PIXI;

mixin(qc, {
    // 版本
    PHASER_VERSION: Phaser.VERSION,

    // 多个游戏实例时使用
    GAMES: Phaser.GAMES,

    // 渲染方式
    AUTO:     Phaser.AUTO,
    CANVAS:   Phaser.CANVAS,
    WEBGL:    Phaser.WEBGL,
    HEADLESS: Phaser.HEADLESS,

    // 方向
    NONE:  Phaser.NONE,
    LEFT:  Phaser.LEFT,
    RIGHT: Phaser.RIGHT,
    UP:    Phaser.UP,
    DOWN:  Phaser.DOWN,

    /**
     * 缩放模式
     *
     * @property {Object} scaleModes
     * @property {Number} scaleModes.DEFAULT = LINEAR
     * @property {Number} scaleModes.LINEAR Smooth scaling
     * @property {Number} scaleModes.NEAREST Pixelating scaling
     */
    scaleModes: Phaser.scaleModes
});

/**
 * Created by wudm on 9/10/15.
 */

this.qc = this.qc || {};
this.qc.VERSION = '1.0.2';

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 日志系统
 * @class qc.Log
 */
var Log = qc.Log = function() {
    var self = this;

    /**
     * @property {boolean} 是否开启trace打印
     */
    self.enableTrace = false;
};

/*
 * 普通打印日志
 * @param arguments
 */
Log.prototype.trace = function() {
    if (!this.enableTrace) return;

    var content = arguments[0];
    for (var i = 1; i < arguments.length; i++) {
        var reg = new RegExp('\\{' + (i - 1) + '\\}', 'g');
        content = content.replace(reg, arguments[i]);
    }
    console.log(content);
};

/**
 * 重要的打印日志
 * @param arguments
 */
Log.prototype.important = function() {
    var content = arguments[0];
    for (var i = 1; i < arguments.length; i++) {
        var reg = new RegExp('\\{' + (i - 1) + '\\}', 'g');
        content = content.replace(reg, arguments[i]);
    }
    console.log('%c' + content, 'color:green');
};

/**
 * 错误日志
 * @param arguments
 */
Log.prototype.error = function() {
    var content = arguments[0];
    for (var i = 1; i < arguments.length; i++) {
        var reg = new RegExp('\\{' + (i - 1) + '\\}', 'g');
        content = content.replace(reg, arguments[i]);
    }
    console.log('%c' + content, 'color:red');
    // 打印错误堆栈
    for (var i = 1; i < arguments.length; i++) {
        if (arguments[i] && arguments[i].stack) {
            console.error(arguments[i].stack);
            return;
        }
    }
    console.trace();
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */
qc.Circle = Phaser.Circle;

/**
 * 类名
 */
Object.defineProperty(qc.Circle.prototype, 'class', {
    get : function() { return 'qc.Circle'; }
});

/**
 * 序列化
 */
qc.Circle.prototype.toJson = function() {
    return [this.x, this.y, this.diameter];
}

/**
 * 反序列化
 */
qc.Circle.prototype.fromJson = function(v) {
    this.x = v[0];
    this.y = v[1];
    this.diameter = v[2];
}

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */
qc.Ellipse = Phaser.Ellipse;

/**
 * 类名
 */
Object.defineProperty(qc.Ellipse.prototype, 'class', {
    get : function() { return 'qc.Ellipse'; }
});

/**
 * 序列化
 */
qc.Ellipse.prototype.toJson = function() {
    return [this.x, this.y, this.width, this.height];
}

/**
 * 反序列化
 */
qc.Ellipse.prototype.fromJson = function(v) {
    this.x = v[0];
    this.y = v[1];
    this.width = v[2];
    this.height = v[3];
}

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */
qc.Line = Phaser.Line;

/**
 * 类名
 */
Object.defineProperty(qc.Line.prototype, 'class', {
    get : function() { return 'qc.Line'; }
});

/**
 * 序列化
 */
qc.Line.prototype.toJson = function() {
    return [this.start.x, this.start.y, this.end.x, this.end.y];
}

/**
 * 反序列化
 */
qc.Line.prototype.fromJson = function(v) {
    this.start.setTo(v[0], v[1]);
    this.end.setTo(v[2], v[3]);
}


/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */
qc.Matrix = Phaser.Matrix;

/**
 * 类名
 */
Object.defineProperty(qc.Matrix.prototype, 'class', {
    get : function() { return 'qc.Matrix'; }
});

/**
 * 序列化
 */
qc.Matrix.prototype.toJson = function() {
    return this.toArray();
}

/**
 * 反序列化
 */
qc.Matrix.prototype.fromJson = function(v) {
    this.fromArray(v);
}

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */
qc.Point = Phaser.Point;

/**
 * 类名
 */
Object.defineProperty(qc.Point.prototype, 'class', {
    get : function() { return 'qc.Point'; }
});

/**
 * 序列化
 */
qc.Point.prototype.toJson = function() {
    return [this.x, this.y];
}

/**
 * 反序列化
 */
qc.Point.prototype.fromJson = function(v) {
    this.x = v[0];
    this.y = v[1];
}

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */
qc.Polygon = Phaser.Polygon;

/**
 * 类名
 */
Object.defineProperty(qc.Polygon.prototype, 'class', {
    get : function() { return 'qc.Polygon'; }
});

/**
 * 序列化
 */
qc.Polygon.prototype.toJson = function() {
    return [this.toNumberArray(), this.closed];
}

/**
 * 反序列化
 */
qc.Polygon.prototype.fromJson = function(v) {
    this.setTo(v[0]);
    this.closed = v[1];
}

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */
qc.Rectangle = Phaser.Rectangle;

/**
 * 类名
 */
Object.defineProperty(qc.Rectangle.prototype, 'class', {
    get : function() { return 'qc.Rectangle'; }
});

/**
 * 序列化
 */
qc.Rectangle.prototype.toJson = function() {
    return [this.x, this.y, this.width, this.height];
}

/**
 * 反序列化
 */
qc.Rectangle.prototype.fromJson = function(v) {
    this.setTo(v[0], v[1], v[2], v[3]);
}

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */
qc.RoundedRectangle = Phaser.RoundedRectangle;

/**
 * 类名
 */
Object.defineProperty(qc.RoundedRectangle.prototype, 'class', {
    get : function() { return 'qc.RoundedRectangle'; }
});

/**
 * 序列化
 */
qc.RoundedRectangle.prototype.toJson = function() {
    return [this.x, this.y, this.width, this.height, this.radius];
}

/**
 * 反序列化
 */
qc.RoundedRectangle.prototype.fromJson = function(v) {
    this.x = v[0];
    this.y = v[1];
    this.width = v[2];
    this.height = v[3];
    this.radius = v[4];
}

/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 曲线的关键帧
 * @class qc.Keyframe
 * @param {number} time - 时间点
 * @param {number} value - 值
 * @param {number | null} inTangent - 进入角度的 Tangent 值
 * @param {number | null} outTangent - 出发角度的 Tangent 值
 */
var Keyframe = qc.Keyframe = function(time, value, inTangent, outTangent){
    /**
     * @property {number} time - 帧所在时间点
     */
    this.time = time;
    /**
     * @property {number}
     */
    this.value = value;
    /**
     * @property {number} type - 当前关键帧的类型
     * @type {number}
     */
    this.type = Keyframe.FREE_SMOOTH;

    this.inTangent = isNaN(inTangent) ? 0 : inTangent;
    this.outTangent = isNaN(outTangent) ? 0 : outTangent;
};
Keyframe.prototype = {};
Keyframe.prototype.constructor = Keyframe;

Keyframe.prototype.clone = function() {
    var other = new Keyframe(this.time, this.value, this.inTangent, this.outTangent);
    other.type = this.type;
    return other;
};

/**
 * 自动
 * @constant
 * @type {number}
 */
Keyframe.AUTO = 0;
/**
 * 平滑
 * @constant
 * @type {number}
 */
Keyframe.FREE_SMOOTH = 1;
/**
 * 水平
 * @constant
 * @type {number}
 */
Keyframe.FLAT = 2;
/**
 * 破裂
 * @constant
 * @type {number}
 */
Keyframe.BROKEN = 3;

Keyframe.InfinityValue = Math.tan(Math.PI / 2);

Object.defineProperties(Keyframe.prototype, {
    /**
     * @property {number} inAngle - 进入角度
     */
    inAngle : {
        get : function() {
            return Math.atan(this.inTangent);
        },
        set : function(v) {
            this.inTangent = Math.tan(v * Math.PI / 180);
        }
    },
    /**
     * @property {number} outAngle - 离开角度
     */
    outAngle : {
        get : function() {
            return Math.atan(this.outTangent);
        },
        set : function(v) {
            this.outTangent = Math.tan(v * Math.PI / 180);
        }
    },

    /**
     * 曲线进入角度的 tangent 值
     * @type {number}
     */
    inTangent : {
        get : function() { return this._inTangent || 0; },
        set : function(v) {
            // 因为 Math.PI 的精度问题，如果设置值为Math.tan(Math.PI/2)则设置为Infinity
            this._inTangent = (Math.abs(v) === Keyframe.InfinityValue) ? ((v < 0 ? -1 : 1) * Infinity) : v;
        }
    },

    /**
     * 曲线出发角度的 tangent 值
     * @type {number}
     */
    outTangent : {
        get : function() { return this._outTangent || 0; },
        set : function(v) {
            // 因为 Math.PI 的精度问题，如果设置值为Math.tan(Math.PI/2)则设置为Infinity
            this._outTangent = (Math.abs(v) === Keyframe.InfinityValue) ? ((v < 0 ? -1 : 1) * Infinity) : v;
        }
    },

    /**
     * @property {boolean} isFlat - 是否是平坦的
     */
    isFlat : {
        get : function() {
            return this.inTangent === this.outTangent && this.outTangent === 0;
        }
    },

    /**
     * @property {boolean} isBroken - 进入角度和离开角度不是平滑的
     */
    isBroken : {
        get : function() {
            return this.inTangent !== this.outTangent;
        }
    }
});

/**
 * 动画曲线
 * 提供三次方贝兹曲线
 * @class qc.BezierCurve
 */
var BezierCurve = qc.BezierCurve = function(keys) {

    /**
     * 记录帧
     * @type {[qc.Keyframe]}
     * @private
     */
    this._keys = [];

    // 添加帧
    if (Array.isArray(keys)) {
        for (var idx in keys) {
            this.addKey(keys[idx]);
        }
    }
    else {
        for (var idx = 0; idx < arguments.length; ++idx) {
            this.addKey(arguments[idx]);
        }
    }
};

BezierCurve.prototype = {};
BezierCurve.prototype.constructor = BezierCurve;

Object.defineProperties(BezierCurve.prototype, {
    /**
     * @property {string}
     */
    class : {
        get : function() { return 'qc.BezierCurve'; }
    },

    /**
     * @property {[qc.Keyframe]} keys - 所有的帧,
     * @readonly
     */
    keys : {
        get : function() { return this._keys; },
        set : function(keys) {
            this._keys = [];

            for (var idx in keys) {
                this.addKey(keys[idx]);
            }
        }
    },

    /**
     * @property {number} length - 包含的帧数量
     */
    length : {
        get : function() { return this._keys.length; }
    },
    /**
     * @property {number} startTime - 曲线开始的时间
     */
    startTime : {
        get : function() {
            if (!this.keys || !this.keys.length)
                return 0;
            else
                return this.keys[0].time;
        }
    },
    /**
     * @property {number} endTime - 曲线结束的时间
     */
    endTime : {
        get : function() {
            if (!this.keys || !this.keys.length)
                return 0;
            else
                return this.keys[this.keys.length - 1].time;
        }
    },
    /**
     * @property {number} totalTime - 曲线经历的时间
     */
    totalTime : {
        get : function() {
            if (!this.keys || !this.keys.length)
                return 0;
            else
                return this.keys[this.keys.length - 1].time - this.keys[0].time;
        }
    },

    /**
     * @property {number} postWrapMode - 延续下去的类型
     */
    postWrapMode : {
        get : function() { return isNaN(this._postWrapMode) ? BezierCurve.WRAP_CLAMP : this._postWrapMode; },
        set : function (v) {
            this._postWrapMode = v;
        }
    },
    /**
     * @property {number} preWrapMode - 延续进来的类型
     */
    preWrapMode : {
        get : function() { return isNaN(this._preWrapMode) ? BezierCurve.WRAP_CLAMP : this._preWrapMode; },
        set : function(v) {
            this._preWrapMode = v;
        }
    }
});

/**
 * 序列化
 */
BezierCurve.prototype.toJson = function() {
    var jsonData = [];
    for (var idx = 0; idx < this._keys.length; ++idx) {
        jsonData.push([this._keys[idx].time, this._keys[idx].value,
            this._keys[idx].inTangent === Infinity ? 'Infinity' : this._keys[idx].inTangent,
            this._keys[idx].outTangent === Infinity ? 'Infinity' : this._keys[idx].outTangent]);
    }
    jsonData.push([this.preWrapMode, this.postWrapMode]);
    return jsonData;
};

/**
 * 反序列化
 */
BezierCurve.prototype.fromJson = function(v) {
    for (var idx = 0; idx < v.length; ++idx) {
        if (v[idx].length === 4) {
            this.addKey(v[idx][0], v[idx][1], 
                v[idx][2] === 'Infinity' ? Infinity : v[idx][2], 
                v[idx][3] === 'Infinity' ? Infinity : v[idx][3])
        }
    }

    if (v[v.length - 1].length === 2) {
        this.preWrapMode = v[v.length - 1][0];
        this.postWrapMode = v[v.length - 1][1];
    }
};

/**
 * 如果在外部修改了节点，需要调用 restore 进行处理
 */
BezierCurve.prototype.sort = function() {
    this._keys.sort(function(a, b){
        return a.time - b.time;
    });
};

/**
 * 添加一帧，如果已经存在相同时间点的帧，则替换
 * @param time {number} - 时间
 * @param value {number} - 值
 * @param inTangent {number} - 进入值
 * @param outTangent {number} - 触发值
 */
BezierCurve.prototype.addKey = function(time, value, inTangent, outTangent) {
    var needCalcTangent = !(time instanceof Keyframe) && (isNaN(inTangent) || isNaN(outTangent));
    var keyframe = time instanceof Keyframe ? time : new Keyframe(time, value, inTangent, outTangent);
    var insertIdx = this._searchInsertIndex(this._keys, keyframe, this._keyframeLess);
    var idx = insertIdx < 0 ? (this._keys.push(keyframe) - 1) : (
        this._keys.splice(insertIdx, this._keys[insertIdx].time === keyframe ? 1 : 0, keyframe) && insertIdx
    );
    needCalcTangent && this.makeKeyframeAuto(idx);
    return idx;
};

/**
 * 删除一帧
 * @param index {number} - 位置
 */
BezierCurve.prototype.removeKey = function(index) {
    return this._keys ? this._keys.splice(index, 1) : null;
};

/**
 * 帧比较
 * @param one
 * @param two
 * @returns {boolean}
 * @private
 */
BezierCurve.prototype._keyframeLess = function(one, two) {
    return one.time < two.time;
};

/**
 * 查找插入点
 * @param array
 * @param value
 * @param less
 * @returns {number}
 * @private
 */
BezierCurve.prototype._searchInsertIndex = function(array, value, less) {
    var low = 0;
    var high = array.length - 1;
    if (high < 0) {
        return -1;
    }
    var middle = 0;
    while (low < high) {
        middle = Math.floor((low + high) / 2);
        less(array[middle], value) ? (low = middle + 1) : (high = middle);
    }
    return less(array[low], value) ? -1 : low;
};

/**
 * 计算值
 * @param t
 * @param v1
 * @param r
 * @param v2
 * @param u
 * @returns {*}
 * @private
 */
BezierCurve.prototype._calc = function(t, v1, r, v2, u) {
    var v = Math.pow(1-t, 3) * v1 + 3 * Math.pow(1-t,2) * t * r + 3 * (1-t) * Math.pow(t, 2) * u + v2 * Math.pow(t, 3);
    //var v = Math.pow(1-t, 2) * v1 + 2 * (1-t) * t * r + v2 * Math.pow(t, 2);
    if (v === Infinity || isNaN(v) || Math.abs(v) >= 0x7fffffff)
        return t === 1 ? v2 : v1;
    return v;
};

/**
 * 利用导数计算斜率
 * @param t
 * @param v1
 * @param r
 * @param v2
 * @param u
 * @returns {number}
 * @private
 */
BezierCurve.prototype._calcDerivative = function(t, v1, r, v2, u) {
    var t1 = 1 - t;
    var v = - 3 * v1 * t1 * t1 + 3 * r * t1 * (1 - 3 * t) + 3 * u * t *(2 - 3 * t) + 3 * v2 * Math.pow(t, 2);
    if (v === Infinity || isNaN(v))
        return 0;
    return v;
};

/**
 * 计算指定时间点的值，控制点长度为阶段长度的1/3
 * @param time
 * @param loop
 */
BezierCurve.prototype.evaluate = function(time, loop) {
    var idx = 0, len = this._keys ? this._keys.length : 0;
    if (len === 0)
        return NaN;
    var regionStart = this._keys[0].time;
    var end = this._keys[len - 1].time;
    var loopLen = end - regionStart;
    // 不在时间区间内
    if (regionStart > time) {
        if (loopLen === 0 || this.preWrapMode === BezierCurve.WRAP_CLAMP) {
            return this._keys[0].value;
        }
        else if (this.preWrapMode === BezierCurve.WRAP_LOOP) {
            var off = regionStart - time;
            off = off - Math.floor(off / loopLen) * loopLen;
            return this.evaluate(end - off, loop);
        }
        else if (this.preWrapMode === BezierCurve.WRAP_PINGPONG) {
            var off = regionStart - time;
            off = off - Math.floor(off / (2 * loopLen)) * 2 * loopLen;
            if (off <= loopLen) {
                return this.evaluate(regionStart + off, loop);
            }
            else {
                return this.evaluate(end - off + loopLen, loop);
            }
        }
    }

    if (time > end) {
        if (loopLen === 0 || this.postWrapMode === BezierCurve.WRAP_CLAMP) {
            return this._keys[len - 1].value;
        }
        else if (this.postWrapMode === BezierCurve.WRAP_LOOP) {
            var off = time - end;
            off = off - Math.floor(off / loopLen) * loopLen;
            return this.evaluate(regionStart + off, loop);
        }
        else if (this.postWrapMode === BezierCurve.WRAP_PINGPONG) {
            var off = time - end;
            off = off - Math.floor(off / (2 * loopLen)) * 2 * loopLen;
            if (off <= loopLen) {
                return this.evaluate(end - off, loop);
            }
            else {
                return this.evaluate(regionStart + off - loopLen, loop);
            }
        }
    }

    if (time === regionStart && loop && this.preWrapMode !== BezierCurve.WRAP_CLAMP) {
        time = end;
    }

    var regionEnd;
    while (++idx < len) {
        regionEnd = this._keys[idx].time;
        if (regionEnd === regionStart) {
            continue;
        }
        if (time <= regionEnd) {
            var tLen = regionEnd - regionStart;
            var yLen = this._keys[idx].value - this._keys[idx - 1].value;
            var currX = (time - regionStart) / tLen;
            //是否需要将 x 对应的 t 计算出来, 利用一元三次方程求解公式算出指定点的 x 值
            //var q = 0.125 - currX / 4;
            //var t1 = (q < 0 ? 1 : -1) * Math.pow(Math.abs(q), 1/3);
            // currX = t1 + 1/2;
            var calcValue = this._calc(currX, this._keys[idx - 1].value, this._keys[idx - 1].value + tLen * this._keys[idx - 1].outTangent / 3,
                this._keys[idx].value, this._keys[idx].value - tLen * this._keys[idx].inTangent / 3);
            return calcValue;
        }
        regionStart = this._keys[idx].time;
    }

    return NaN;
};

/**
 * 计算指定时间点切线的斜率
 * @param time
 */
BezierCurve.prototype.evaluateDerivative = function(time) {
    var idx = 0, len = this._keys ? this._keys.length : 0;
    if (len === 0)
        return NaN;
    var regionStart = this._keys[0].time;
    // 不在时间区间内
    if (regionStart > time || time > this._keys[len - 1].time)
        return NaN;
    var regionEnd;
    while (++idx < len) {
        regionEnd = this._keys[idx].time;
        if (regionEnd === regionStart) {
            continue;
        }
        if (time < regionEnd) {
            var tLen = regionEnd - regionStart;
            //var yLen = (this._keys[idx].value - this._keys[idx - 1].value) / tLen;
            var currX = (time - regionStart) / tLen;
            //是否需要将 x 对应的 t 计算出来, 利用一元三次方程求解公式算出指定点的 x 值
            //var q = 0.125 - currX / 4;
            //var t1 = (q < 0 ? 1 : -1) * Math.pow(Math.abs(q), 1/3);
            // currX = t1 + 1/2;
            var derivative = this._calcDerivative(currX, this._keys[idx - 1].value, this._keys[idx - 1].value + tLen * this._keys[idx - 1].outTangent / 3,
                this._keys[idx].value, this._keys[idx].value - tLen * this._keys[idx].inTangent / 3);
            return derivative / tLen;
        }
        regionStart = this._keys[idx].time;
    }

    return NaN;
};

/**
 * 将指定关键帧的入角和出角设置为自动
 * @param idx
 */
BezierCurve.prototype.makeKeyframeAuto = function(idx) {
    var currFrame = idx >= 0 && idx < this._keys.length ? this.keys[idx] : null;
    var preFrame = idx > 0 && idx < this._keys.length ? this._keys[idx - 1] : null;
    var nextFrame = idx < this._keys.length - 1 && idx >= 0 ? this._keys[idx + 1] : null;
    var count = 0;
    var angle = 0;
    if (!currFrame) {
        return;
    }
    if (preFrame) {
        count++;
        angle += Math.atan2(currFrame.value - preFrame.value, currFrame.time - preFrame.time);
    }
    if (nextFrame) {
        count++;
        angle += Math.atan2(nextFrame.value - currFrame.value, nextFrame.time - currFrame.time);
    }
    if (count > 0)
        currFrame.inTangent = currFrame.outTangent = Math.tan(angle / count);
};

/**
 * 将指定关键帧的入角和出角设置为平滑
 * @param idx
 */
BezierCurve.prototype.makeKeyframeSmooth = function(idx) {
    var currFrame = idx >= 0 && idx < this._keys.length ? this.keys[idx] : null;
    if (!currFrame) {
        return;
    }
    var angle = Math.atan(currFrame.inTangent) + Math.atan(currFrame.outTangent);
    currFrame.inTangent = currFrame.outTangent = Math.tan(angle / 2);
};

/**
 * 将指定关键帧的入角和出角设置为水平
 * @param idx
 */
BezierCurve.prototype.makeKeyframeFlat = function(idx) {
    var currFrame = idx >= 0 && idx < this._keys.length ? this.keys[idx] : null;
    if (!currFrame) {
        return;
    }
    currFrame.inTangent = currFrame.outTangent = 0;
};

BezierCurve.prototype.clone = function() {
    var other = new BezierCurve([]);

    for (var idx in this._keys) {
        other.addKey(this._keys[idx].clone());
    }

    return other;
};

/**
 * 固定值拓展
 * @constant
 * @type {number}
 */
BezierCurve.WRAP_CLAMP = 0;
/**
 * 循环拓展
 * @constant
 * @type {number}
 */
BezierCurve.WRAP_LOOP = 1;
/**
 * 乒乓拓展
 * @constant
 * @type {number}
 */
BezierCurve.WRAP_PINGPONG = 2;
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 图形处理工具
 */
var GeometricTool = qc.GeometricTool = {};

/**
 * 将多边形的顶点信息放入一个数组
 * @param points
 * @returns {Array}
 */
GeometricTool.flattenPolygon = function (points) {
    var flatten =  new Array(points.length * 2);
    var len = points.length;
    while (len--) {
        flatten[2 * len] = points[len].x;
        flatten[2 * len + 1] = points[len].y;
    }
    return flatten;
};

/**
 * 判定点是否在多边形内
 * @param points
 * @param x
 * @param y
 * @returns {boolean}
 */
GeometricTool.polygonContains = function(points, x, y) {
    //  Adapted from http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html by Jonas Raoni Soares Silva
    if (isNaN(points[0])) {
        var length = points.length;
        var inside = false;
        for (var i = -1, j = length - 1; ++i < length; j = i) {
            var ix = points[i].x;
            var iy = points[i].y;
            var jx = points[j].x;
            var jy = points[j].y;

            if (((iy <= y && y < jy) || (jy <= y && y < iy)) && (x < (jx - ix) * (y - iy) / (jy - iy) + ix))
            {
                inside = !inside;
            }
        }
        return inside;
    }
    else {
        var length = points.length / 2;
        var inside = false;
        for (var i = -1, j = length - 1; ++i < length; j = i) {
            var ix = points[2 * i];
            var iy = points[2 * i + 1];
            var jx = points[2 * j];
            var jy = points[2 * j + 1];

            if (((iy <= y && y < jy) || (jy <= y && y < iy)) && (x < (jx - ix) * (y - iy) / (jy - iy) + ix))
            {
                inside = !inside;
            }
        }
        return inside;
    }
};

/**
 * 点在线上的判定
 * @param a
 * @param b
 * @param x
 * @param y
 * @returns {boolean}
 */
GeometricTool.pointOnLine = function (a, b, x, y) {
    return ((x - a.x) * (b.y - a.y) === (b.x - a.x) * (y - a.y));
};

/**
 * 点在线段上的判定
 * @param a
 * @param b
 * @param x
 * @param y
 * @returns {boolean}
 */
GeometricTool.pointOnSegment = function (a, b, x, y) {

    var xMin = Math.min(a.x, b.x);
    var xMax = Math.max(a.x, b.x);
    var yMin = Math.min(a.y, b.y);
    var yMax = Math.max(a.y, b.y);

    return (qc.GeometricTool.pointOnLine(a, b, x, y) && (x >= xMin && x <= xMax) && (y >= yMin && y <= yMax));
};

/**
 * 线段相交
 * @param a
 * @param b
 * @param e
 * @param f
 * @param asSegment
 * @param result
 * @returns {*}
 */
GeometricTool.lineIntersectsPoints = function (a, b, e, f, asSegment, result) {

    if (typeof asSegment === 'undefined') { asSegment = true; }
    if (!result) { result = new qc.Point(); }

    var a1 = b.y - a.y;
    var a2 = f.y - e.y;
    var b1 = a.x - b.x;
    var b2 = e.x - f.x;
    var c1 = (b.x * a.y) - (a.x * b.y);
    var c2 = (f.x * e.y) - (e.x * f.y);
    var denom = (a1 * b2) - (a2 * b1);

    if (denom === 0)
    {
        return null;
    }
    if (a.equals(e) || a.equals(f)) {
        result.x = a.x;
        result.y = a.y;
        return result;
    }
    else if (b.equals(e) || b.equals(f)) {
        result.x = b.x;
        result.y = b.y;
        return result;
    }
    result.x = ((b1 * c2) - (b2 * c1)) / denom;
    result.y = ((a2 * c1) - (a1 * c2)) / denom;

    if (asSegment)
    {
        var uc = ((f.y - e.y) * (b.x - a.x) - (f.x - e.x) * (b.y - a.y));
        var ua = (((f.x - e.x) * (a.y - e.y)) - (f.y - e.y) * (a.x - e.x)) / uc;
        var ub = (((b.x - a.x) * (a.y - e.y)) - ((b.y - a.y) * (a.x - e.x))) / uc;

        if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)
        {
            return result;
        }
        else
        {
            return null;
        }
    }

    return result;
};

var inside = GeometricTool._sutherlandHodgmanInside = function (cp1, cp2, p, delta) {
    return (cp2.x-cp1.x)*(p.y-cp1.y) + delta >= (cp2.y-cp1.y)*(p.x-cp1.x);
};

var intersection = GeometricTool._sutherlandHodgmanIntersection = function (cp1, cp2, s, e) {
    var dc = [cp1.x - cp2.x, cp1.y - cp2.y],
        dp = [s.x - e.x, s.y - e.y],
        n1 = cp1.x * cp2.y - cp1.y * cp2.x,
        n2 = s.x * e.y - s.y * e.x,
        n3 = 1.0 / (dc[0] * dp[1] - dc[1] * dp[0]);
    return {x : (n1*dp[0] - n2*dc[0]) * n3, y : (n1*dp[1] - n2*dc[1]) * n3};
};
/**
 * 基于Sutherland Hodgman 算法的多边形裁切
 * @param subjectPolygon
 * @param clipPolygon
 * @returns {[]}
 * @constructor
 */
GeometricTool.sutherlandHodgman = function (subjectPolygon, clipPolygon) {
    var cp1, cp2, s, e, j, i;
    var delta = 0.000001;
    var outputList = subjectPolygon;
    cp1 = clipPolygon[clipPolygon.length-1];
    for (j in clipPolygon) {
        var cp2 = clipPolygon[j];
        var inputList = outputList;
        outputList = [];
        s = inputList[inputList.length - 1]; //last on the input list
        for (i in inputList) {
            var e = inputList[i];
            if (inside(cp1, cp2, e, delta)) {
                if (!inside(cp1, cp2, s, delta)) {
                    outputList.push(intersection(cp1, cp2, s, e));
                }
                outputList.push(e);
            }
            else if (inside(cp1, cp2, s, delta)) {
                outputList.push(intersection(cp1, cp2, s, e));
            }
            s = e;
        }
        cp1 = cp2;
    }
    return outputList
};

/**
 * 将多边形分解为三角形
 * @param p
 * @returns {[]}
 * @constructor
 */
GeometricTool.Triangulate = function(p)
{
    var sign = true;

    var n = p.length;
    if(n < 3) return [];

    var tgs = [];
    var avl = [];
    for(var i = 0; i < n; i++) avl.push(i);

    i = 0;
    var al = n;
    while(al > 3)
    {
        var i0 = avl[(i+0)%al];
        var i1 = avl[(i+1)%al];
        var i2 = avl[(i+2)%al];

        var ax = p[i0].x,  ay = p[i0].y;
        var bx = p[i1].x,  by = p[i1].y;
        var cx = p[i2].x,  cy = p[i2].y;

        var earFound = false;
        if(PIXI.PolyK._convex(ax, ay, bx, by, cx, cy, sign))
        {
            earFound = true;
            for(var j = 0; j < al; j++)
            {
                var vi = avl[j];
                if(vi === i0 || vi === i1 || vi === i2) continue;

                if(PIXI.PolyK._PointInTriangle(p[vi].x, p[vi].y, ax, ay, bx, by, cx, cy)) {
                    earFound = false;
                    break;
                }
            }
        }

        if(earFound)
        {
            tgs.push(i0, i1, i2);
            avl.splice((i+1)%al, 1);
            al--;
            i = 0;
        }
        else if(i++ > 3*al)
        {
            // need to flip flip reverse it!
            // reset!
            if(sign)
            {
                tgs = [];
                avl = [];
                for(i = 0; i < n; i++) avl.push(i);

                i = 0;
                al = n;

                sign = false;
            }
            else
            {
                return null;
            }
        }
    }

    tgs.push(avl[0], avl[1], avl[2]);
    return tgs;
};


/**
 * 使用https://github.com/schteppe/poly-decomp.js为基础，摘出多边形分解算法，修改点的构成方式
 */
/**
 * 计算三角形面积
 * @param a
 * @param b
 * @param c
 * @returns {number}
 */
GeometricTool.area = function(a,b,c){
    if (!a || !b || !c) {
        return 0;
    }
    return (((b.x - a.x) * (c.y - a.y)) - ((c.x - a.x) * (b.y - a.y)));
};

/**
 * 判定点是否在线的左侧
 * @param a
 * @param b
 * @param c
 * @returns {boolean}
 */
GeometricTool.left = function(a,b,c){
    return GeometricTool.area(a,b,c) > 0;
};

/**
 * 判定点是否在线的左侧或者线上
 * @param a
 * @param b
 * @param c
 * @returns {boolean}
 */
GeometricTool.leftOn = function(a,b,c) {
    return GeometricTool.area(a, b, c) >= 0;
};

/**
 * 判定点是否在线的右侧
 * @param a
 * @param b
 * @param c
 * @returns {boolean}
 */
GeometricTool.right = function(a,b,c) {
    return GeometricTool.area(a, b, c) < 0;
};

/**
 * 判定点是否在线的右侧，或者线上
 * @param a
 * @param b
 * @param c
 * @returns {boolean}
 */
GeometricTool.rightOn = function(a,b,c) {
    return GeometricTool.area(a, b, c) <= 0;
};

/**
 * 计算两点的距离
 * @param a
 * @param b
 * @returns {number}
 */
GeometricTool.distance = function(a,b){
    var dx = b.x - a.x;
    var dy = b.y - a.y;
    return dx * dx + dy * dy;
};

/**
 * 获取多边形的顶点
 * @param a
 * @param b
 * @param c
 * @returns {boolean}
 */
GeometricTool.atPolygon = function(polygon, i){
    var s = polygon.length;
    return polygon[i < 0 ? i % s + s : i % s];
};

/**
 * 计算两线的交点
 * @param l1
 * @param l2
 * @param precision
 */
GeometricTool.lineInt = function(l1, l2, precision) {
    precision = precision || 0;
    var i = new qc.Point();
    var a1, b1, c1, a2, b2, c2, det;
    a1 = l1[1].y - l1[0].y;
    b1 = l1[0].x - l1[1].x;
    c1 = a1 * l1[0].x + b1 * l1[0].y;
    a2 = l2[1].y - l2[0].y;
    b2 = l2[0].x - l2[1].x;
    c2 = a2 * l2[0].x + b2 *l2[0].y;
    det = 1 / (a1 * b2 - a2 * b1);
    if (!GeometricTool._equals(det, 0, precision)) {
        i.x = (b2 * c1 - b1 * c2) * det;
        i.y = (a1 * c2 - a2 * c1) * det;
    }
    return i;
};

/**
 * 多边形指定顶点是否是逆时针起点
 * @param polygon
 * @param i
 * @returns {boolean}
 */
GeometricTool.isPolygonReflex = function(polygon, i){
    return GeometricTool.right(
        GeometricTool.atPolygon(polygon, i - 1),
        GeometricTool.atPolygon(polygon, i),
        GeometricTool.atPolygon(polygon, i + 1));
};

GeometricTool.polygonCanSee = function(polygon, a, b) {
    var p, dist, l1 = [], l2 = [];
    if (GeometricTool.leftOn(
            GeometricTool.atPolygon(polygon, a + 1),
            GeometricTool.atPolygon(polygon, a),
            GeometricTool.atPolygon(polygon, b)) &&
        GeometricTool.rightOn(
            GeometricTool.atPolygon(polygon, a -1),
            GeometricTool.atPolygon(polygon, a),
            GeometricTool.atPolygon(polygon, b))) {
        return false;
    }
    dist = GeometricTool.distance(GeometricTool.atPolygon(polygon, a), GeometricTool.atPolygon(polygon, b));
    for (var i = 0; i != polygon.length; ++i) {
        if ((i + 1) % polygon.length === a || i === a)
            continue;
        if (GeometricTool.leftOn(
                GeometricTool.atPolygon(polygon, a),
                GeometricTool.atPolygon(polygon, b),
                GeometricTool.atPolygon(polygon, i + 1)
            ) &&
            GeometricTool.rightOn(
                GeometricTool.atPolygon(polygon, a),
                GeometricTool.atPolygon(polygon, b),
                GeometricTool.atPolygon(polygon, i)
            )
        ) {
            l1[0] = GeometricTool.atPolygon(polygon, a);
            l1[1] = GeometricTool.atPolygon(polygon, b);
            l2[0] = GeometricTool.atPolygon(polygon, i);
            l2[1] = GeometricTool.atPolygon(polygon, i + 1);
            p = GeometricTool.lineInt(l1, l2);
            if (GeometricTool.distance(GeometricTool.atPolygon(polygon, a), p) < dist) {
                return false;
            }
        }
    }
    return true;
};

/**
 * 判定两点是否相等
 * @param a
 * @param b
 * @param precision
 * @returns {boolean}
 * @private
 */
GeometricTool._equals = function(a,b,precision){
    precision = precision || 0;
    return Math.abs(a-b) < precision;
};

/**
 * 获取两个线段的交点
 * @param p1
 * @param p2
 * @param q1
 * @param q2
 * @param delta
 * @returns {{x: number, y: number}}
 */
GeometricTool.getIntersectionPoint = function(p1, p2, q1, q2, delta){
    delta = delta || 0;
    var a1 = p2.y - p1.y;
    var b1 = p1.x - p2.x;
    var c1 = (a1 * p1.x) + (b1 * p1.y);
    var a2 = q2.y - q1.y;
    var b2 = q1.x - q2.x;
    var c2 = (a2 * q1.x) + (b2 * q1.y);
    var det = (a1 * b2) - (a2 * b1);

    if(!GeometricTool._equals(det,0,delta))
        return { x : ((b2 * c1) - (b1 * c2)) / det, y : ((a1 * c2) - (a2 * c1)) / det};
    else
        return { x : 0, y : 0};
};

/**
 * 将一个多边形分解为多个凸多边形
 * @param polygon
 * @param result
 * @param reflexVertices
 * @param steinerPoints
 * @param delta
 * @param maxlevel
 * @param level
 * @returns {*}
 */
GeometricTool.quickDecomp = function(polygon, result,reflexVertices,steinerPoints,delta,maxlevel,level){
    maxlevel = maxlevel || 100;
    level = level || 0;
    delta = delta || 25;
    result = typeof(result)!="undefined" ? result : [];
    reflexVertices = reflexVertices || [];
    steinerPoints = steinerPoints || [];

    var upperInt=[0,0], lowerInt=[0,0], p=[0,0]; // Points
    var upperDist=0, lowerDist=0, d=0, closestDist=0; // scalars
    var upperIndex=0, lowerIndex=0, closestIndex=0; // Integers
    var lowerPoly=[], upperPoly=[]; // polygons
    var poly = polygon;

    if(poly.length < 3) return result;

    level++;
    if(level > maxlevel){
        return result;
    }

    for (var i = 0; i < poly.length; ++i) {
        if (GeometricTool.isPolygonReflex(poly, i)) {
            reflexVertices.push(poly[i]);
            upperDist = lowerDist = Number.MAX_VALUE;

            for (var j = 0; j < poly.length; ++j) {
                if (GeometricTool.left(GeometricTool.atPolygon(poly, i - 1), GeometricTool.atPolygon(poly, i), GeometricTool.atPolygon(poly, j))
                    && GeometricTool.rightOn(GeometricTool.atPolygon(poly, i - 1), GeometricTool.atPolygon(poly, i), GeometricTool.atPolygon(poly, j - 1))) { // if line intersects with an edge
                    p = GeometricTool.getIntersectionPoint(GeometricTool.atPolygon(poly, i - 1), GeometricTool.atPolygon(poly, i), GeometricTool.atPolygon(poly, j), GeometricTool.atPolygon(poly, j - 1)); // find the point of intersection
                    if (GeometricTool.right(GeometricTool.atPolygon(poly, i + 1), GeometricTool.atPolygon(poly, i), p)) { // make sure it's inside the poly
                        d = GeometricTool.distance(poly[i], p);
                        if (d < lowerDist) { // keep only the closest intersection
                            lowerDist = d;
                            lowerInt = p;
                            lowerIndex = j;
                        }
                    }
                }
                if (GeometricTool.left(GeometricTool.atPolygon(poly, i + 1), GeometricTool.atPolygon(poly, i), GeometricTool.atPolygon(poly, j + 1))
                    && GeometricTool.rightOn(GeometricTool.atPolygon(poly, i + 1), GeometricTool.atPolygon(poly, i), GeometricTool.atPolygon(poly, j))) {
                    p = GeometricTool.getIntersectionPoint(GeometricTool.atPolygon(poly, i + 1), GeometricTool.atPolygon(poly, i), GeometricTool.atPolygon(poly, j), GeometricTool.atPolygon(poly, j + 1));
                    if (GeometricTool.left(GeometricTool.atPolygon(poly, i - 1), GeometricTool.atPolygon(poly, i), p)) {
                        d = GeometricTool.distance(poly[i], p);
                        if (d < upperDist) {
                            upperDist = d;
                            upperInt = p;
                            upperIndex = j;
                        }
                    }
                }
            }

            // if there are no vertices to connect to, choose a point in the middle
            if (lowerIndex == (upperIndex + 1) % poly.length) {
                //console.log("Case 1: Vertex("+i+"), lowerIndex("+lowerIndex+"), upperIndex("+upperIndex+"), poly.size("+this.vertices.length+")");
                p.x = (lowerInt.x + upperInt.x) / 2;
                p.y = (lowerInt.y + upperInt.y) / 2;
                steinerPoints.push(p);

                if (i < upperIndex) {
                    //lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.begin() + upperIndex + 1);
                    Array.prototype.push.apply(lowerPoly, poly.slice(i, upperIndex + 1));
                    lowerPoly.push(p);
                    upperPoly.push(p);
                    if (lowerIndex != 0){
                        //upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.end());
                        Array.prototype.push.apply(upperPoly, poly.slice(lowerIndex, poly.length));
                    }
                    //upperPoly.insert(upperPoly.end(), poly.begin(), poly.begin() + i + 1);
                    Array.prototype.push.apply(upperPoly, poly.slice(0, i + 1));
                } else {
                    if (i != 0){
                        //lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.end());
                        Array.prototype.push.apply(lowerPoly, poly.slice(i, poly.length));
                    }
                    //lowerPoly.insert(lowerPoly.end(), poly.begin(), poly.begin() + upperIndex + 1);
                    Array.prototype.push.apply(lowerPoly, poly.slice(0, upperIndex + 1));
                    lowerPoly.push(p);
                    upperPoly.push(p);
                    //upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.begin() + i + 1);
                    Array.prototype.push.apply(upperPoly, poly.slice(lowerIndex, i + 1));
                }
            } else {
                // connect to the closest point within the triangle
                //console.log("Case 2: Vertex("+i+"), closestIndex("+closestIndex+"), poly.size("+this.vertices.length+")\n");

                if (lowerIndex > upperIndex) {
                    upperIndex += poly.length;
                }
                closestDist = Number.MAX_VALUE;

                if(upperIndex < lowerIndex){
                    return result;
                }

                for (var j = lowerIndex; j <= upperIndex; ++j) {
                    if (GeometricTool.leftOn(GeometricTool.atPolygon(poly, i - 1), GeometricTool.atPolygon(poly, i), GeometricTool.atPolygon(poly, j))
                        && GeometricTool.rightOn(GeometricTool.atPolygon(poly, i + 1), GeometricTool.atPolygon(poly, i), GeometricTool.atPolygon(poly, j))) {
                        d = GeometricTool.distance(GeometricTool.atPolygon(poly, i), GeometricTool.atPolygon(poly, j));
                        if (d < closestDist) {
                            closestDist = d;
                            closestIndex = j % poly.length;
                        }
                    }
                }

                if (i < closestIndex) {
                    Array.prototype.push.apply(lowerPoly, poly.slice(i, closestIndex + 1));
                    if (closestIndex != 0){
                        Array.prototype.push.apply(upperPoly, poly.slice(closestIndex, poly.length));
                    }
                    Array.prototype.push.apply(upperPoly, poly.slice(0, i + 1));
                } else {
                    if (i != 0){
                        Array.prototype.push.apply(lowerPoly, poly.slice(i, poly.length));
                    }
                    Array.prototype.push.apply(lowerPoly, poly.slice(0, closestIndex + 1));
                    Array.prototype.push.apply(upperPoly, poly.slice(closestIndex, i + 1));
                }
            }

            // solve smallest poly first
            if (lowerPoly.length < upperPoly.length) {
                GeometricTool.quickDecomp(lowerPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level);
                GeometricTool.quickDecomp(upperPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level);
            } else {
                GeometricTool.quickDecomp(upperPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level);
                GeometricTool.quickDecomp(lowerPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level);
            }

            return result;
        }
    }
    result.push(poly);

    return result;
};

/**
 * 分隔为凸多边形
 */
GeometricTool.decomp = function(polygon){
    var edges = GeometricTool.getCutEdges(polygon);
    if (edges.length > 0)
        return GeometricTool.polygonSlice(polygon, edges);
    else
        return [polygon];
};

/**
 * 根据裁切边界分隔多边形
 * @param polygon
 * @param cutEdges
 * @returns {*}
 */
GeometricTool.polygonSlice = function(polygon, cutEdges) {
    if (cutEdges.length === 0) return [this];
    if (cutEdges instanceof Array &&
        cutEdges.length &&
        cutEdges[0] instanceof Array &&
        cutEdges[0].length == 2 &&
        cutEdges[0][0] instanceof  Array
    ) {
        var polys = [polygon];
        for (var i = 0; i < cutEdges.length; i++) {
            var cutEdge = cutEdges[i];
            for (var j = 0; j < polys.length; j++) {
                var poly = polys[j];
                var result = GeometricTool.polygonSlice(poly, cutEdge);
                if (result) {
                    polys.splice(j , 1);
                    polys.push(result[0], result[1]);
                    break;
                }
            }
        }
        return polys;
    } else {
        var cutEdge = cutEdges;
        var i = polygon.indexOf(cutEdge[0]);
        var j = polygon.indexOf(cutEdge[1]);
        if (i != -1 && j != -1) {
            return [
                GeometricTool.copyPolygon(polygon, i, j),
                GeometricTool.copyPolygon(polygon, j, 1)
            ];
        }
        else {
            return false;
        }
    }
};

/**
 * 拷贝多边形
 * @param polygon
 * @param i
 * @param j
 * @param targetPoly
 * @returns {*|Array}
 */
GeometricTool.copyPolygon = function(polygon, i, j, targetPoly) {
    var p = targetPoly || [];
    if (i < j) {
        for (var k = i; k <= j; k++) {
            p.push(polygon[k]);
        }
    }
    else {
        for (var k = 0; k <= j; k++) {
            p.push(polygon[k]);
        }
        for (var k = i; k < polygon.length; k++) {
            p.push(polygon[k]);
        }
    }
    return p;
};

/**
 * 得到裁剪边界
 * @param polygon
 * @returns {Array}
 */
GeometricTool.getCutEdges = function(polygon) {
    var min = [], tmp1 = [], tmp2 = [], tmpPoly = [];
    var nDiags = Number.MAX_VALUE;

    for (var i = 0; i < polygon.length; ++i) {
        if (GeometricTool.isPolygonReflex(polygon, i)) {
            for (var j = 0; j < polygon.length; ++j) {
                if (GeometricTool.polygonCanSee(polygon, i, j)) {
                    tmp1 = GeometricTool.getCutEdges(GeometricTool.copyPolygon(polygon, i, j));
                    tmp2 = GeometricTool.getCutEdges(GeometricTool.copyPolygon(polygon, j, i));

                    for (var k = 0; k < tmp2.length; k++) {
                        tmp1.push(tmp2[k]);
                    }
                    if (tmp1.length < nDiags) {
                        min = tmp1;
                        nDiags = tmp1.length;
                        min.push([
                            GeometricTool.atPolygon(polygon, i),
                            GeometricTool.atPolygon(polygon, j)
                        ]);
                    }
                }
            }
        }
    }
    return min;
};
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 资源的工具接口
 * @internal
 */
var AssetUtil = qc.AssetUtil = {
    /**
     * 几种资源类型，在meta文件中需要指明
     */
    ASSET_SCENE  : 1,
    ASSET_PREFAB : 2,
    ASSET_ATLAS  : 3,
    ASSET_TEXT   : 4,
    ASSET_FONT   : 5,
    ASSET_SOUND  : 6,
    ASSET_EXCEL  : 7,
    ASSET_WEBFONT : 8,
    ASSET_JS     : 100,
    ASSET_UNKNOWN : 101,

    /**
     * 解析资源
     * @internal
     */
    parse : function(game, assetInfo, nextStep) {
        // 进行解压
        var files, key = assetInfo.key, url = assetInfo.url;
        if (!/\.bin$/.test(url)) {
            // 非 bin 资源
            if (assetInfo.isRaw) {
                // 图片资源，增加到缓存层
                this._addAtlasToCache(game, key, url);
            }
            else {
                // 只有声音这种类型了，将声音添加到声音管理器中
                var sound = game.assets._cache.getSound(url);
                if (sound) {
                    var asset = new qc.SoundAsset(key, url, sound.data, {uuid: key, type: AssetUtil.ASSET_SOUND});
                    game.assets.cache(key, url, asset);
                }
            }
            if (nextStep) nextStep();
            return;
        }
        else if (!assetInfo.isSound) {
            // json 方式
            var data = game.assets._cache.getText(url);
            delete game.assets._cache._text[url];

            try {
                files = JSON.parse(data);
            }
            catch (e)
            {
                game.log.error('Asset{0}/{1} Parse fail', key, url);
                qc.Util.popupError(e.message); 
                if (nextStep) nextStep();
                return;
            }
        }
        else {
            // 二进制方式
            var data = game.assets._cache.getBinary(url);
            delete game.assets._cache._binary[url];

            var rawData, cursor;
            var dataView = new Uint8Array(data); // 建立视图

            if (dataView[0] === 0x51 && dataView[1] === 0x43) {
                // concat 类型
                rawData = dataView;
                cursor = 2;
            }
            else {
                game.log.error('Asset({0}) parse fail', url);
                game.assets._parsing--;
                return;
            }

            var nextBlockInfo = function() {
                var bodyLen = (rawData[cursor] << 24) |
                    (rawData[cursor + 1] << 16) |
                    (rawData[cursor + 2] << 8) |
                    rawData[cursor + 3];
                var bodyIndex = cursor + 4;
                cursor += bodyLen + 4;
                return [bodyIndex, bodyLen];
            };

            // 声音文件，meta 跟 binary 的音乐资源
            files = [];
            var slice;
            slice = nextBlockInfo();
            files.push(game.serializer.unpackUTF8(rawData.subarray(slice[0], slice[0] + slice[1])));
            slice = nextBlockInfo();
            files.push(data.slice(slice[0], slice[1]));
        }

        // JSON 解析出meta内容
        var meta = JSON.parse(files[0]);
        switch (meta.type) {
        case AssetUtil.ASSET_ATLAS:
            return AssetUtil._parseAtlas(game, meta, JSON.parse(files[1]), files[2],
                files[3] ? JSON.parse(files[3]) : undefined, key, url, nextStep);
        case AssetUtil.ASSET_PREFAB:
            return AssetUtil._parsePrefab(game, JSON.parse(files[1]), key, url, meta, nextStep);
        case AssetUtil.ASSET_SCENE:
            return AssetUtil._parseScene(game, JSON.parse(files[1]), key, url, meta, nextStep);
        case AssetUtil.ASSET_TEXT:
            return AssetUtil._parseText(game, files[1], key, url, meta, nextStep);
        case AssetUtil.ASSET_FONT:
            return AssetUtil._parseFont(game, meta, files[1], files[2], key, url, nextStep);
        case AssetUtil.ASSET_SOUND:
            return AssetUtil._parseSound(game, meta, files[1], key, url, nextStep);
        case AssetUtil.ASSET_EXCEL:
            return AssetUtil._parseExcel(game, meta, JSON.parse(files[1]), key, url, nextStep);
        case AssetUtil.ASSET_WEBFONT:
            return AssetUtil._parseWebFont(game, meta, JSON.parse(files[1]), key, url, nextStep);
        default:
            throw new Error('unsupported asset type：' + meta.type);
        }
    },

    /**
     * @param url 资源下载路径
     * @returns {*|boolean} 当前文件是否是音乐资源
     */
    isSound : function(url) {
        if (qc.Util.isArray(url)) {
            for (var i = 0; i < url.length; i++) {
                if (url[i] && /\.(mp3|ogg|mp3\.bin|ogg\.bin)$/.test(url[i].toLowerCase()))
                    return true;
            }
            return false;
        }
        return (url && /\.(mp3|ogg|mp3\.bin|ogg\.bin)$/.test(url.toLowerCase()));
    },

    // 解析图集
    _parseAtlas : function(game, meta, json, image, ani, key, url, nextStep) {
        var img = new Image();
        img.src = "data:image/png;base64," + image;

        // 等待图片加载完毕
        img.onerror = function() {
            img.onerror = undefined;
            nextStep();
        };
        img.onload = function() {
            img.onload = undefined;

            // 缓存起来（先写到phaser缓存中）
            var c = game.assets._cache;
            {
                c._images[url] = { url : url, data : img };
                PIXI.BaseTextureCache[url] = new PIXI.BaseTexture(img);
                PIXI.TextureCache[url] = new PIXI.Texture(PIXI.BaseTextureCache[url]);
                if (Object.keys(json).length > 0) {
                    if (ani &&
                        (meta.animationType === Sprite.DRAGON_BONES ||
                        meta.animationType === Sprite.FRAME_SAMPLES)) {
                        // 骨骼动画的JSON比较特殊，需要特殊转换下
                        var json2 = qc.dragonBones.CoverAtlas(json);
                        c._images[url].frameData = Phaser.AnimationParser.JSONDataHash(game.phaser, json2, url);
                    }
                    else {
                        if (json && qc.Util.isArray(json.frames))
                            c._images[url].frameData =
                                Phaser.AnimationParser.JSONData(game.phaser, json, url);
                        else
                            c._images[url].frameData =
                                Phaser.AnimationParser.JSONDataHash(game.phaser, json, url);
                    }
                }
                else {
                    c._images[url].frameData = new Phaser.FrameData();
                    c._images[url].frame = new Phaser.Frame(0, 0, 0, img.width, img.height, '', '');
                    c._images[url].frameData.addFrame(new Phaser.Frame(0, 0, 0, img.width, img.height, null,
                        game.math.uuid()));
                }
                c._resolveURL(url, c._images[url]);
            }
            var atlas = new qc.Atlas(key, url, c._images[url], meta, ani);
            atlas.json = json;
            atlas.img = img;
            game.assets.cache(key, url, atlas);

            // 调用回调
            nextStep();
        };
    },

    // 注册一张图片到游戏中（包括注册给 PIXI / PHASER / QC）
    addAtlasFromImage : function(game, key, url, img) {
        // 注册给 PIXI、PHASER
        game.assets._cache.addImage(url, url, img);

        // 注册给 QC
        return this._addAtlasToCache(game, key, url);
    },

    // 注册一张图片给 QC 的缓存层
    _addAtlasToCache : function(game, key, url) {
        // 注册给 QC
        var imgData = game.assets._cache._images[url];
        var atlas = new qc.Atlas(key, url, imgData, {
            uuid : game.math.uuid(),
            type : AssetUtil.ASSET_ATLAS
        });
        atlas.img = imgData.data;
        game.assets.cache(key, url, atlas);
        return atlas;
    },

    // 解析字体
    _parseFont : function(game, meta, image, xml, key, url, nextStep) {
        // xml转换下
        xml = game.assets._loader.parseXml(xml);

        var img = new Image();
        img.src = "data:image/png;base64," + image;
        // 等待图片加载完毕
        img.onerror = function() {
            img.onerror = undefined;
            nextStep();
        };
        img.onload = function() {
            img.onload = undefined;
            var font = new qc.Font(key, url, img, xml, meta);
            font._fontFamily = qc.UIText.BITMAPFONT;
            game.assets._cache.addBitmapFont(url, url, img, xml, font.xSpacing, font.ySpacing);
            game.assets.cache(key, url, font);
            nextStep();
        };
    },

    // 解析字符串
    _parseText : function(game, text, key, url, meta, nextStep) {
        game.assets._cache.addText(url, url, text);
        game.assets.cache(key, url, new qc.TextAsset(key, url, text, meta));
        nextStep();
    },

    // 解析场景
    _parseScene : function(game, scene, key, url, meta, nextStep) {
        return AssetUtil._parsePrefab(game, scene, key, url, meta, nextStep);
    },

    // 解析预制
    _parsePrefab : function(game, prefab, key, url, meta, nextStep) {
        // 缓存起来
        game.assets.cache(key, url, new Prefab(key, url, prefab, meta));
        nextStep();
    },

    // 解析声音
    _parseSound : function(game, meta, sound, key, url, nextStep) {
        // 将声音添加到声音管理器中
        game.assets._cache.addSound(url, url, sound);

        // 判断声音是否已经解码
        if (game.assets._cache.isSoundDecoded(key)) {
            var asset = new qc.SoundAsset(key, url, game.phaser.cache.getSound(url), meta);
            game.assets.cache(key, url, asset);
            nextStep();
        }
        else {
            // 解码声音
            game.sound.phaser.decode(url, {url : url, meta : meta, nextStep : nextStep} );
        }
    },

    // 解析表格数据
    _parseExcel : function(game, meta, data, key, url, nextStep) {
        // 缓存
        game.assets.cache(key, url, new qc.ExcelAsset(key, url, data, meta));
        nextStep();
    },

    // 解析webFont
    _parseWebFont : function(game, meta, fontUrl, key, url, nextStep) {
        AssetUtil._deleteWebFont(meta.uuid);
        AssetUtil._loadWebFont(game, meta.uuid, fontUrl.url);
        var font = new qc.Font(meta.uuid, url, font, null, meta);
        if (typeof fontUrl.url === 'string') {
            font._fontUrl = [fontUrl.url];
        }
        else
            font._fontUrl = fontUrl.url;

        font._fontFamily = qc.UIText.WEBFONT;

        game.assets.cache(key, url.toString(), font);
        nextStep();

        // webfontloader.js是第三方包，需要判断是否加载
        if (window.WebFont) {
            WebFont.load({
                timeout: 60000,
                custom: {
                    families: [meta.uuid]
                },
                fontactive: function(fontName) {
                    // 此时有可能游戏世界尚未创建，因此需要添加此判断
                    if (game.world) {
                        // 清除相关的字体缓存信息
                        Object.keys(PIXI.Text.fontPropertiesCache).forEach(function(font) {
                           if (font.indexOf(meta.uuid) >= 0) {
                               delete PIXI.Text.fontPropertiesCache[font];
                           }
                        });
                        // 通知相关节点更新
                        AssetUtil._refreshWebFont(fontName, game.world);
                    }

                    // 扔出事件
                    game.assets.webFontLoaded.dispatch();
                },
                fontinactive: function(fontName) {
                    game.log.error("Load " + fontName + " fail");
                }
            });
        }
    },

    _webFontType: {
        "eot" : "embedded-opentype",
        "ttf" : "truetype",
        "ttc" : "truetype",
        "woff" : "woff",
        "svg" : "svg"
    },

    // 加载WebFont字体
    _loadWebFont: function(game, name, url) {
        var TYPE = AssetUtil._webFontType;
        var fontStyle = document.createElement("style");
        fontStyle.type = "text/css";
        fontStyle.id = "qc.webfont." + name;
        document.getElementsByTagName('head')[0].appendChild(fontStyle);

        var fontStr = "@font-face { font-family:" + name + "; src:";

        if (qc.Util.isArray(url)) {
            for (var i = 0, li = url.length; i < li; i++) {
                var src = url[i];
                var type = AssetUtil._getSuffix(url[i]);

                if (url[i].indexOf('http') >= 0)
                    fontStr += "url('" + url[i] + "') format('" + TYPE[type] + "')";
                else
                    // 相对路径
                    fontStr += "url('" + (game.assets.baseURL + url[i]) + "') format('" + TYPE[type] + "')";
                fontStr += (i === li - 1) ? ";" : ",";
            }
        }
        else {
            var type = AssetUtil._getSuffix(url);
            fontStr += "url('" + url + "') format('" + TYPE[type] + "');";
        }
        fontStyle.textContent += fontStr + "};";
    },

    // 删除WebFont字体
    _deleteWebFont: function(name) {
        while (true) {
            var font = document.getElementById("qc.webfont." + name);
            if (font)
                qc.Util.removeHTML(font);
            else
                break;
        }
    },

    // 字体加载完成后更新字体
    _refreshWebFont : function(fontName, node) {
        if (node instanceof qc.UIText) {
            node._refreshWebFont(fontName);
        }
        var children = node.children;
        var len = children.length;
        while (len--) {
            var child = children[len];
            AssetUtil._refreshWebFont(fontName, child);
        }
    },

    // 获取文件后缀
    _getSuffix : function(url) {
        var extension = url.substr((Math.max(0, url.lastIndexOf(".")) || Infinity) + 1);
        return extension.toLowerCase();
    },

    // Functions to create xhrs
    _createStandardXHR : function() {
        try {
            return new window.XMLHttpRequest();
        } catch( e ) {}
    },

    _createActiveXHR : function() {
        try {
           return new window.ActiveXObject( "Microsoft.XMLHTTP" );
        } catch( e ) {}
    },

    // 取得 xhr 对象
    getXHR : function() {
        var loc;
        // #8138, IE may throw an exception when accessing
        // a field from window.location if document.domain has been set
        try {
                loc = location.href;
        } catch( e ) {
                // Use the href attribute of an A element
                // since IE will modify it given document.location
                loc = document.createElement( "a" );
                loc.href = "";
                loc = loc.href;
        }
        var rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/;
        var rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/;
        var locParts = rurl.exec( loc.toLowerCase() ) || [];
        var isLocal = rlocalProtocol.test(locParts[1]);

        var xhr = window.ActiveXObject ?
	/* Microsoft failed to properly
	 * implement the XMLHttpRequest in IE7 (can't request local files),
	 * so we use the ActiveXObject when it is available
	 * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
	 * we need a fallback.
	 */
	function() {
		return !isLocal && AssetUtil._createStandardXHR() || AssetUtil._createActiveXHR();
	} :
	// For all other browsers, use the standard XMLHttpRequest object
	AssetUtil._createStandardXHR;

        return xhr();
    },

    // Get请求
    get : function(url, onload, onerror) {
        var xhr = AssetUtil.getXHR();

        xhr.open('GET', url, true);
        xhr.responseType = 'text';

        xhr.onload = function(){
            return onload(xhr.responseText);
        }

        xhr.onerror = function(){
            if (onerror) onerror(xhr);
        }

        xhr.send();
    },

    // Post 请求
    post : function(url, strData, onload, onerror) {
        var xhr = AssetUtil.getXHR();

        xhr.open('POST', url, true);
        xhr.responseType = 'text';

        xhr.onload = function(){
            return onload(xhr.responseText);
        }

        xhr.onerror = function(){
            if (onerror) onerror(xhr);
        }

        xhr.send(strData);
    },
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 资源管理
 ** 在phaser底层，所有的资源key全部使用url
 * @class qc.Assets
 * @constructor
 * @internal
 */
var Assets = qc.Assets = function(game, loader, cache) {
    this._loader = loader;
    this._cache = cache;
    this.game = game;
    loader._qc = this;
    cache._qc = this;

    // 记录当前配置中的 uuid 与 url
    this._uuid2UrlConf = window.urlMapConfig || {};
    this._uuid2UrlConf['__builtin_resource__'] = '__builtin_resource__';

    /**
     * @property {number} maxRetryTimes - 资源下载失败时，最大重试次数
     */
    this.maxRetryTimes = 1;

    /**
     * @property {qc.Signal} webFontLoaded - web字体库加载完毕的回调
     */
    this.webFontLoaded = new qc.Signal();

    // 当前解析中的资源数量
    this._parsing = 0;

    // 使用一个互斥量来避免多次设置 Timer
    this._loaderTimerMutex = -1024;

    // 资源缓存，记录url与解析后的资源信息
    this._assets = {};

    // 记录key与url的映射关系
    this._keyUrl = {};

    // 注册加载成功的回调
    this._complete = {};

    var self = this;
    loader.onFileComplete.add(function(progress, key, success) {
        if (!success) {
            // 下载失败了，首先进行重试下载
            var assetsInfos = self._complete[key];
            if (!assetsInfos || assetsInfos.length === 0) return;

            if (assetsInfos[0].retry >= self.maxRetryTimes) {
                // 满额了
                self.game.log.important('Asset({0}) load fail', key);
                self._callCb(key, null, assetsInfos[0]);
                return;
            }

            // 延后继续重试
            self.game.timer.add(1000, function() {
                self.game.log.trace('Retry download:{0}', key);
                assetsInfos[0].retry++;
                self._internalLoad(assetsInfos[0]);
            });
            return;
        }
        self._parseAsset(key);
    });

    // 注册声音解码完成回调
    game.sound.phaser.onSoundDecode.add(function(key, data) {
        var asset = new qc.SoundAsset(key, data.url, self.game.phaser.cache.getSound(key), data.meta);
        game.assets.cache(key, data.url, asset);

        data.nextStep();
    });

    // 加载内置资源
    this._loadBuiltin();
};
Assets.prototype = {};
Assets.prototype.constructor = Assets;

// 资源类型定义
/**
 * 贴图类型
 * @constant
 * @type {number}
 */
Assets.IMAGE = 1;

/**
 * 声音文件
 * @constant
 * @type {number}
 */
Assets.AUDIO = 2;

/**
 * 文本
 * @constant
 * @type {number}
 */
Assets.TEXT = 3;

/**
 * 字体文件
 * @constant
 * @type {number}
 */
Assets.BITMAPFONT = 4;

/**
 * 二进制数据
 * @constant
 * @type {number}
 */
Assets.BINARY = 5;

/**
 * 图集(url格式描述 json hash)
 * @constant
 * @type {number}
 */
Assets.ATLAS = 6;

/**
 * json文本
 * @constant
 * @type {nubmer}
 */
Assets.JSON = 7;

// 属性定义
Object.defineProperties(Assets.prototype, {
    /**
     * @property {boolean} isLoading - 是否正在加载资源
     * @readonly
     */
    'isLoading' : {
        get : function() { return this._loader.isLoading; }
    },

    /**
     * @property {boolean} hasLoaded - 所有资源是否已经全部加载完成
     * @readonly
     */
    'hasLoaded' : {
        get : function() { return this._loader.hasLoaded; }
    },

    /**
     * @property {string} baseURL - 资源的域地址
     */
    'baseURL' : {
        get : function()  { return this._loader.baseURL; },
        set : function(v) { this._loader.baseURL = v;    }
    },

    /**
     * @property {boolean} parsing - 当前是不是有资源正在解析中
     * @internal
     */
    parsing : {
        get : function() {
            return this._parsing > 0;
        }
    },

    /**
     * @property {number} loaded - 已经加载完毕的资源数量
     * @readonly
     */
    loaded : {
        get : function() { return this._loader._loadedFileCount; }
    },

    /**
     * @property {number} total - 总的需要加载的资源量
     * @readonly
     */
    total : {
        get : function() { return this._loader._totalFileCount; }
    }
});

/**
 * 批量加载
 * @param items {array} - 资源信息数组
 * @param callback - 全部加载完毕的回调
 */
Assets.prototype.loadBatch = (function(){
    var loadItem = function(assets, item, items, callback) {
        item.loaded = false;
        assets.load(item.key, item.url, function(asset){
            item.loaded = true;
            item.asset = asset;
            // 单个回调
            if (item.callback) item.callback(item);
            // 检测是否都加载完毕
            for (var i = 0; i < items.length; i++) {
                if (!items[i].loaded) {
                    return;
                }
            }
            // 全部加载完毕，执行回调
            callback(items);
        }, item.override, item.isRaw);
    };
    return function(items, callback) {
        for (var i = 0; i < items.length; i++) {
            loadItem(this, items[i], items, callback);
        }
    };
})();

/**
 * 开始加载资源，assetInfo指明了详细的资源信息
 * @private
 */
Assets.prototype._internalLoad = function(assetInfo) {
    var self = this;

    // 初始化重试次数
    if (!assetInfo.retry) assetInfo.retry = 0;

    // 先看下是不是声音资源
    // 音乐文件置换为该设备可以播放的音乐 url
    assetInfo.isSound = AssetUtil.isSound(assetInfo.url);
    if (assetInfo.isSound)
        assetInfo.url = self.game.sound.tryGetUrl(assetInfo.url);
    var url = assetInfo.url;

    // 先从缓存中查找，有的话就直接返回了
    if (!assetInfo.override) {
        var asset = self.find(url);
        if (asset) {
            // 更新下URL映射
            self._keyUrl[assetInfo.key] = url;
            if (assetInfo.callback)
                assetInfo.callback(asset);
            return;
        }
    }

    // 没有就需要进行加载了

    if (assetInfo.key === '__builtin_resource__')
        // 内置资源，不需要进行加载，_complete 事件由 _loadBuiltin 中 _parseAssets 成功之后触发
        return;

    // 成功后需要回调通知
    var loader = self._loader;
    if (assetInfo.retry === 0) {
        if (!self._complete[url])
            self._complete[url] = [assetInfo];
        else
            self._complete[url].push(assetInfo);
    }

    // 加载需要区分3种：声音资源、原始资源和打包资源
    if (assetInfo.isSound) {
        if (url.indexOf('.bin') < 0)
            // 使用 audio tag 方式加载
            loader.audio(url, url);
        else if (AssetUtil.isSound(url))
            // 使用 web audio 方式加载
            loader.binary(url, url);
    }
    else {
        if (assetInfo.isRaw)
            loader.image(url, url);
        else
            loader.text(url, url);
    }
    self.start();
};

/**
 * 请求加载图片资源，加载成功后调用回调通知
 *
 * @param key {string|undefined} - 需要保证资源ID的唯一。不指定时使用url作为key
 * @param url {string} - 资源的地址（可选参数）
 * @param callback - 成功加载的回调
 * @param override - 是否无视是否已缓存强制加载（成功加载后覆盖资源）
 */
Assets.prototype.load = function() {
    var self = this;
    var key, url, callback, override = false, isRaw = false, inPrefab = false;
    if (arguments.length === 1) {
        // 原型为：load(url)
        url = arguments[0];
    }
    else if (arguments.length === 2) {
        if (typeof arguments[1] === 'string') {
            // 原型为：load(key, url)
            key = arguments[0];
            url = arguments[1];
        }
        else {
            // 原型为：load(url, callback)
            url = arguments[0];
            callback = arguments[1];
        }
    }
    else {
        // 原型为：load(key, url, callback, override)
        key = arguments[0];
        url = arguments[1];
        callback = arguments[2];
        override = arguments[3];
        isRaw = arguments[4];
        inPrefab = arguments[5];
    }
    key = key || url;

    if (!url) {
        throw new Error('url is empty.');
        return;
    }

    // 开始加载资源
    var assetInfo = {
        key: key,
        url: url,
        callback: callback,
        override: override,
        isRaw: isRaw,
        inPrefab: inPrefab,
    };

    // preload 时同步加载，之后阶段都错开1ms异步加载。
    // 这个问题最好的解决方案本应是干掉 Img 的缓存同步加载机制，但是我们不想hack进phaser代码。
    // 具体问题是：
    //    game.preload() { load(a); load(b); }
    //    game.create() { load(c); }
    //    在 preload 时候，加载了 a,b，并且判定成功加载，会设置 isLoading = false，然后
    //    切换到 create() 阶段，此时调度 load(c) 的结果是 c 进入到加载列表中。后续调度弹回到 a
    //    的加载尾端的时候，居然发现还有元素在加载列表，会尝试去加载，可这时候 isLoading = false
    //    不允许加载，进而 warning。
    if (! self.game.phaser.state._created)
        self._internalLoad(assetInfo);
    else {
        self.game.timer.add(1, function() {
            self._internalLoad(assetInfo);
        });
    }
};

/**
 * @param uuid
 * @param callback
 * @param override
 * @internal
 */
Assets.prototype.loadByUUID = function(uuid, callback, override, inPrefab) {
    var url = this._uuid2UrlConf[uuid];
    if (!url) {
        this.game.log.important('UUID({0}) not found.', uuid);
        if (callback) callback();
        return;
    }

    this.load(uuid, url, callback, override, false, inPrefab);
};

/**
 * @param key 资源ID，需要保证唯一
 * @param img 原始图片url地址或者 image对象
 */
Assets.prototype.loadTexture = function() {
    var key, url, img, callback;
    if (arguments.length === 1) {
        if (typeof arguments[0] === 'string') {
            // loadTexture(url)
            url = arguments[0];
        }
        else {
            // loadTexture(IMG)
            img = arguments[0];
        }
    }
    else if (arguments.length === 2) {
        if (typeof arguments[0] === 'string' && typeof arguments[1] === 'function') {
            // loadTexture(url, callback)
            url = arguments[0];
            callback = arguments[1];
        }
        else if (typeof arguments[1] === 'function') {
            // loadTexture(IMG, callback)
            img = arguments[0];
            callback = arguments[1];
        }
        else {
            // loadTexture(key, url)
            key = arguments[0];
            url = arguments[1];
        }
    }
    else if (typeof arguments[1] === 'string') {
        // loadTexture(key, url, callback)
        key = arguments[0];
        url = arguments[1];
        callback = arguments[2];
    }
    else {
        // loadTexture(key, img, callback)
        key = arguments[0];
        img = arguments[1];
        callback = arguments[2];
    }
    key = key || url;

    if (img) {
        // 已经加载完毕的图片
        if (!key) {
            throw new Error('key cannot be null.');
            return;
        }
        this._keyUrl[key] = key;
        var atlas = AssetUtil.addAtlasFromImage(this.game, key, key, img);
        callback(atlas);
    }
    else {
        // 走标准的加载流程，指明为raw
        this.load(key, url, callback, false, true);
    }
};

/**
 * 查找某个资源（如果资源尚未成功下载，返回null）
 * @method qc.Assets#find
 * @param key {string} - 资源的标识（可以传资源url进去）
 */
Assets.prototype.find = function(key) {
    if (qc.Util.isArray(key)) {
        for (var i = 0; i < key.length; i++) {
            var asset = this.find(key[i]);
            if (asset) return asset;
        }
    }
    else {
        var url = this._keyUrl[key];
        return url ? this._assets[url] : null;
    }
};

/**
 * 释放指定资源
 * @method qc.Assets#unload
 * @param asset {*} - 资源对象或者资源url或者资源的key
 */
Assets.prototype.unload = function(asset) {
    if (typeof asset === 'string' && !this._keyUrl[asset]) {
        // 对于声音，需要先转换下，因为url可能发生变化了
        var isSound = AssetUtil.isSound(asset);
        if (isSound)
            asset = this.game.sound.tryGetUrl(asset);
    }

    if (qc.Util.isArray(asset)) {
        for (var i = 0; i < asset.length; i++) {
            this.unload(asset[i]);
        }
    }
    else {
        if (typeof asset === 'string') {
            // 指明了key或者url
            var url = this._keyUrl[asset];
            if (url) {
                if (this._assets[url])
                    this._assets[url].unload(this.game);
                delete this._assets[url];
                delete this._keyUrl[asset];
            }
        }
        else {
            // 指明了资源类型
            this.unload(asset.url);
        }
    }
};

/**
 * 根据uuid查找资源
 * TODO: 如果遍历的效率比较低，需要考虑额外记录一份映射表
 * @method qc.Assets#findByUUID
 */
Assets.prototype.findByUUID = function(uuid) {
    for (var k in this._assets) {
        var asset = this._assets[k];
        if (asset.uuid === uuid)
            // 找到了
            return asset;
    }
};

/**
 * 缓存资源
 * @internal
 */
Assets.prototype.cache = function(key, url, asset) {
    key = key || url;
    this._assets[url] = asset;
    this._keyUrl[key] = url;
    this._keyUrl[url] = url; // 便于查找使用统一方式
};

/**
 * 清理缓存资源
 * @method qc.Assets#clear
 */
Assets.prototype.clear = function() {
    // 清理pixi的缓存
    var self = this;
    var keys = Object.keys(PIXI.TextureCache);
    keys.forEach(function(key) {
        if (key !== '__default' && key !== '__missing' && key !== '__builtin_resource__') {
            PIXI.TextureCache[key].destroy();
            delete PIXI.TextureCache[key];
        }
    });
    keys = Object.keys(PIXI.BaseTextureCache);
    keys.forEach(function(key) {
        if (key !== '__default' && key !== '__missing' && key !== '__builtin_resource__') {
            // 不要强制删除
            //PIXI.BaseTextureCache[key].destroy();
            delete PIXI.BaseTextureCache[key];
        }
    });

    // 清理phaser的缓存
    var builtin = self._cache._images['__builtin_resource__'];
    self._cache.destroy();
    self._cache._images['__builtin_resource__'] = builtin;

    // 干掉我自己的缓存
    keys = Object.keys(self._keyUrl);
    keys.forEach(function(key) {
        if (key !== '__builtin_resource__') delete self._keyUrl[key];
    });
    keys = Object.keys(self._assets);
    keys.forEach(function(key) {
        if (key !== '__builtin_resource__') delete self._assets[key];
    });
};

/**
 * 启动下载
 * @internal
 */
Assets.prototype.start = function() {
    // 使用 timer 而不是直接调用的原因是：
    // Image 加载的时候，如果浏览器开启了缓存，那么设置 Image.src 的时候，complete 属性瞬间
    //     为 true，就不会去走到 onload onerror 等异步回调中，变成同步就加载完毕，具体可以参见
    //     phaser.loader.loadImageTag。导致以下的情境：
    //     game.preload() { load(a); load(b); }
    //     game.create() { }
    //     在 preload 中 load(a) 的时候，立马就执行下载完毕的回调，此时 load(b) 还
    //     没执行，所以等待 加载池为空，进而触发状态切换到 create。
    //     故我们这里延迟 1ms 模拟异步的时序。
    var self = this;
    if (self.game.time.fixedTime - self._loaderTimerMutex < 1)
        // 已经处于加载中
        return;
    self._loaderTimerMutex = self.game.time.fixedTime;
    self.game.timer.add(1, function() {
        self._loaderTimerMutex = -1024;
        self._loader.start();
    });
};

/**
 * 注册一组新的 uuid -> url 的配置（编辑器更新）
 */
Assets.prototype.addUrlConf = function(uuid, url) {
    this._uuid2UrlConf[uuid] = url;
};

/**
 * 解析下载的二进制图片数据
 * @private
 */
Assets.prototype._parseAsset = function(key) {
    // 取得回调列表和资源信息
    var self = this, url = key;
    var assetInfos = self._complete[key];
    if (!assetInfos || assetInfos.length == 0) return;

    var nextStep = function() {
        // 资源解析完毕
        self._parsing--;

        // 如果是预制的话，需要等待依赖资源下载完毕后才能认为成功了
        // 场景也算预制，但其依赖资源在preload中再加载
        var asset = self.find(key);
        if (asset &&
            asset.meta.type !== AssetUtil.ASSET_SCENE &&
            asset instanceof Prefab && asset.hasUnloadedDependence(self.game)) {
            // 加载依赖资源
            for (var i in asset.dependences) {
                var data = asset.dependences[i];
                self.loadByUUID(data.uuid, function(a) {
                    if (!a)
                    {
                        self.game.log.important('预制的依赖资源({0})加载失败.', data.uuid);
                        // 资源加载失败，对应的 uuid 依赖也标识为 ok，免得会一直有依赖的资源没下载完毕
                        var info = asset.dependences[data.uuid];
                        if (info)
                            info.ok = true;
                    }

                    // 检查下是不是都加载完毕了
                    if (asset.hasUnloadedDependence(self.game)) return;

                    // 全部加载完毕，调用回调通知
                    self._callCb(key, asset, assetInfos[0]);
                }, false, true);
            }
            return;
        }

        // 调用回调
        self._callCb(key, asset, assetInfos[0]);
    };

    self.nextStep = nextStep;
    // 解析数据，解析数据可能也是异步的，因此需要将后续处理作为回调传入
    self._parsing++;
    try {
        AssetUtil.parse(this.game, assetInfos[0], nextStep);
    }
    catch (e) {
        // 无法解析，认为资源加载失败了
        self._parsing--;
        self.game.log.error('Asset({0}) Parse fail', key, e);
        qc.Util.popupError(e.message); 
        self._callCb(key, null, assetInfos[0]);
    }
};

/**
 * 资源加载完毕的回调
 * @param key
 * @param asset
 * @private
 */
Assets.prototype._callCb = function(key, asset, assetInfo) {
    var self = this;
    var assetsInfos = self._complete[key];
    if (!assetsInfos) return;
    delete self._complete[key];

    // 某个资源加载完毕(成功或失败)，通知 loading 进度
    if (this.game.loadingProcessCallback)
    {
        if (assetInfo && !assetInfo.inPrefab)
            // 预制中的资源不通知进度，整个预制加载成功通知一次即可
            this.game.loadingProcessCallback(key, asset, assetInfo);
    }

    assetsInfos.forEach(function(assetsInfo) {
        if (asset) {
            // 记录下KEY与URL的映射
            self._keyUrl[assetsInfo.key] = assetsInfo.url;
        }
        if (assetsInfo.callback)
            assetsInfo.callback(asset);
    });
};

/**
 * 加载内置资源
 * @private
 */
Assets.prototype._loadBuiltin = function() {
    var self = this;
    var key = '__builtin_resource__';
    var imgData = ' ';
    var meta = {
        ver: 1,
        uuid: key,
        padding: {
            "button.png": [8,8,8,8],
            "button2.png": [8,8,8,8],
            "slider.png": [8,8,8,8],
            "sliderbg.png": [8,7,8,7],
            "sliderbg2.png": [10,6,2,6]
        }
    };
    var json = JSON.parse('{"frames":{"button.png":{"frame":{"x":30,"y":67,"w":26,"h":23},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":26,"h":23},"sourceSize":{"w":26,"h":23}},"button2.png":{"frame":{"x":2,"y":59,"w":26,"h":23},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":26,"h":23},"sourceSize":{"w":26,"h":23}},"circle.png":{"frame":{"x":2,"y":2,"w":37,"h":37},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":37,"h":37},"sourceSize":{"w":37,"h":37}},"empty.png":{"frame":{"x":41,"y":18,"w":16,"h":16},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":16,"h":16},"sourceSize":{"w":16,"h":16}},"ok.png":{"frame":{"x":2,"y":84,"w":25,"h":22},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":25,"h":22},"sourceSize":{"w":25,"h":22}},"slider.png":{"frame":{"x":34,"y":41,"w":28,"h":24},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":28,"h":24},"sourceSize":{"w":28,"h":24}},"sliderbg.png":{"frame":{"x":2,"y":41,"w":30,"h":16},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":30,"h":16},"sourceSize":{"w":30,"h":16}},"sliderbg2.png":{"frame":{"x":41,"y":2,"w":19,"h":14},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":19,"h":14},"sourceSize":{"w":19,"h":14}}},"meta":{"app":"http://www.codeandweb.com/texturepacker","version":"1.0","image":"__builtin_resource__.png","format":"RGBA8888","size":{"w":64,"h":128},"scale":"1"}}');
    self._parsing++;
    AssetUtil._parseAtlas(this.game, meta, json, imgData, undefined,
        meta.uuid, key, function() {
            self._parsing--;
            self._callCb(key, self.find(key));
        });
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 图集资源的描述
 *
 * @class qc.Atlas
 * @constructor
 * @internal
 */
var Atlas = qc.Atlas = function(key, url, data, meta, ani) {
    /**
     * @property {string} key - 图集的标志，直接使用资源的网址
     * @readonly
     */
    this.key = url;

    /**
     * @property {string} url - 资源的网址
     * @readonly
     */
    this.url = url;

    /**
     * @property {object} meta - meta数据
     * @readonly
     */
    this.meta = meta;

    /**
     * @property {object} animation - 动作信息
     * @readonly
     */
    this.animation = undefined;

    /**
     * @property {object} json - 图集的atlas数据
     * @internal
     */
    this.json = undefined;

    /**
     * @property {Image} img - 图集对应的图片
     */
    this.img = undefined;

    // 记录图集的数据
    this._data = data;

    // 解析动作信息
    if (ani) {
        this.animation = {
            type : meta.animationType,
            data : ani
        }
    }
};
Atlas.prototype.constructor = Atlas;

Object.defineProperties(Atlas.prototype, {
    /**
     * @property {number} count - 图片的数量
     * @readonly
     */
    'count' : {
        get : function() { return this._data.frameData.total; }
    },

    /**
     * @property {Array} frames - 所有的图片信息
     * @readonly
     */
    'frames' : {
        get : function() {
            return this._data.frameData._frames;
        }
    },

    /**
     * @property {Array} frameNames - 图片的名字列表
     * @readonly
     */
    frameNames : {
        get : function() {
            var frames = this.frames;
            if (!frames || frames.length < 2) return [0];
            var list = [];
            for (var i in frames) {
                list.push(frames[i].name);
            }
            return list;
        }
    },

    /**
     * @property {string} uuid - 资源唯一标识符
     * @readonly
     */
    uuid : {
        get : function() { return this.meta.uuid; }
    }
});

/**
 * 根据名字或位置取得某个图片
 *
 * @method qc.Atlas#getFrame
 * @param frame {string|number} - 帧的位置或名字
 */
Atlas.prototype.getFrame = function(frame) {
    if (typeof frame === 'number')
        return this._data.frameData.getFrame(frame);
    return this._data.frameData.getFrameByName(frame);
};

/**
 * 取得某个图片的9宫格信息
 * @param {string|undefined} frame
 * @return [left, top, right, bottom]
 */
Atlas.prototype.getPadding = function(frame) {
    if (!this.meta || !this.meta.padding) return [0, 0, 0, 0];

    // 只有一个图片时，固定返回padding的内容
    if (this.count == 1) {
        var keys = Object.keys(this.meta.padding);
        frame = keys.length > 0 ? keys[0] : 0;
    }

    if (frame === undefined) frame = 0;
    var padding = this.meta.padding[frame];
    if (padding) {
        // 确保为数字
        padding[0] *= 1;
        padding[1] *= 1;
        padding[2] *= 1;
        padding[3] *= 1;
    }
    return padding || [0, 0, 0, 0];
};

/**
 * 释放本资源信息
 * @internal
 */
Atlas.prototype.unload = function(game) {
    game.assets._cache.removeImage(this.key, false);

    delete PIXI.TextureCache[this.key];
    delete PIXI.BaseTextureCache[this.key];
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 预制资源(包含场景)的描述
 *
 * @class qc.Prefab
 * @constructor
 * @internal
 */
var Prefab = qc.Prefab = function(key, url, data, meta) {
    /**
     * @property {string} key - 预制的标志
     * @readonly
     */
    this.key = key;

    /**
     * @property {string} url - 资源的网址
     * @readonly
     */
    this.url = url;

    /**
     * @property {object} meta - meta数据
     * @readonly
     */
    this.meta = meta;

    /**
     * @property {object} json - 预制的数据
     * @internal
     */
    this.json = data;
};
Prefab.prototype.constructor = Prefab;

Object.defineProperties(Prefab.prototype, {
    /**
     * @property {string} uuid - 资源唯一标识符
     * @readonly
     */
    uuid : {
        get : function() { return this.meta.uuid; }
    },

    /**
     * @property {object} dependences - 本资源依赖于其他哪些资源?
     */
    dependences : {
        get : function() {
            return this.json.dependences;
        }
    }
});

/**
 * 当前是不是还有依赖的资源没有加载成功？
 */
Prefab.prototype.hasUnloadedDependence = function(game) {
    for (var i in this.dependences) {
        var data = this.dependences[i];
        if (data.ok) continue;
        if (data.uuid === this.uuid) continue;
        var asset = game.assets.find(data.uuid);
        if (!asset)
            // 还有资源没有加载进来
            return true;

        if (asset instanceof Prefab &&
            asset.hasUnloadedDependence(game))
            return true;

        // 标记此资源已经加载了
        data.ok = true;
    }
    return false;
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 字体的描述
 *
 * @class qc.Font
 * @constructor
 * @internal
 */
var Font = qc.Font = function(key, url, image, xml, meta) {
    /**
     * @property {string} key - 字体的标志
     * @readonly
     */
    this.key = key;

    /**
     * @property {string} url - 资源的网址
     * @readonly
     */
    this.url = url;

    /**
     * @property {object} meta - meta数据
     * @readonly
     */
    this.meta = meta;

    /**
     * @property {object} xml - 字体的数据
     * @readonly
     */
    this.xml = xml;

    /**
     * @property {image} image - 字体图片
     * @readonly
     */
    this.image = image;

    /**
     * @property {array} _fontUrl - webFont的地址
     * @private
     */
    this._fontUrl;

    /**
     * 字体类型
     * @private
     */
    this._fontFamily = qc.UIText.SYSTEMFONT;
};

Font.prototype.constructor = Font;

Object.defineProperties(Font.prototype, {
    /**
     * @property {string} uuid - 资源唯一标识符
     * @readonly
     */
    uuid : {
        get : function() { return this.meta.uuid; }
    },

    /**
     * @property {number} xSpacing
     * @readonly
     */
    xSpacing : {
        get : function() {
            return this.meta.xSpacing;
        }
    },

    /**
     * @property {number} ySpacing
     * @readonly
     */
    ySpacing : {
        get : function() {
            return this.meta.ySpacing;
        }
    }
});

/**
 * 释放字体资源
 * @param game
 * @internal
 */
Font.prototype.unload = function(game) {
    game.assets._cache.removeBitmapData(this.key);
    game.assets._cache.removeBitmapFont(this.key);
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 声音资源的描述
 *
 * @class qc.SoundAsset
 * @constructor
 * @internal
 */
var SoundAsset = qc.SoundAsset = function(key, url, sound,  meta) {
    /**
     * @property {string} key - 直接使用网址作为唯一标识
     * @readonly
     */
    this.key = url;

    /**
     * @property {string} url - 资源的网址
     * @readonly
     */
    this.url = url;

    /**
     * @property {object} meta - meta数据
     * @readonly
     */
    this.meta = meta;

    /**
     * @property {object} sound - 声音信息
     * @readonly
     */
    this.sound = sound;
};
SoundAsset.prototype.constructor = SoundAsset;

Object.defineProperties(SoundAsset.prototype, {
    /**
     * @property {string} uuid - 资源唯一标识符
     * @readonly
     */
    uuid : {
        get : function() { return this.meta.uuid; }
    }
});

/**
 * 释放声音资源
 * @param game
 * @internal
 */
SoundAsset.prototype.unload = function(game) {
    game.assets._cache.removeSound(this.key);
};
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 文本资源的描述
 *
 * @class qc.TextAsset
 * @constructor
 * @internal
 */
var TextAsset = qc.TextAsset = function(key, url, data, meta) {
    /**
     * @property {string} key - 图集的标志
     * @readonly
     */
    this.key = key;

    /**
     * @property {string} url - 资源的网址
     * @readonly
     */
    this.url = url;

    /**
     * @property {object} meta - meta数据
     * @readonly
     */
    this.meta = meta;

    /**
     * @property {string} text - 文本信息
     * @readonly
     */
    this.text = data;
}
TextAsset.prototype.constructor = TextAsset;

Object.defineProperties(TextAsset.prototype, {
    /**
     * @property {string} uuid - 资源唯一标识符
     * @readonly
     */
    uuid : {
        get : function() { return this.meta.uuid; }
    }
});

/**
 * 释放文本资源
 * @param game
 * @internal
 */
SoundAsset.prototype.unload = function(game) {
    game.assets._cache.removeText(this.key);
};


/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 表格资源
 *
 * @class qc.ExcelAsset
 * @constructor
 * @internal
 */
var ExcelAsset = qc.ExcelAsset = function(key, url, data, meta) {
    /**
     * @property {string} key - 图集的标志
     * @readonly
     */
    this.key = key;

    /**
     * @property {string} url - 资源的网址
     * @readonly
     */
    this.url = url;

    /**
     * @property {{}} meta - meta数据
     * @private
     */
    this._meta = meta;

    /**
     * @property {{}} _data - 原始表格数据
     * @private
     */
    this._data = typeof data === 'string' ? JSON.parse(data) : data;

    /**
     * @property {{}} _tables - 整理后的表格数据
     * @private
     */
    this._sheets = {};

    // 构建表格数据
    this._makeSheetData();
};
ExcelAsset.prototype.constructor = ExcelAsset;

Object.defineProperties(ExcelAsset.prototype, {
    /**
     * @property {string} uuid - 资源唯一标识符
     * @readonly
     */
    uuid : {
        get : function() { return this.meta.uuid; }
    },

    /**
     * @property {[string]} sheetsName - 获取所有的表名
     * @readonly
     */
    sheetsName : {
        get : function() { return this._sheets ? Object.keys(this._sheets) : []; }
    },

    /**
     * @property {{}} - sheets - 获取所有表数据
     * @readonly
     */
    sheets : {
        get : function() { return this._sheets; }
    },

    /**
     * @property {{}} meta - 元数据
     * @readonly
     */
    meta :  {
        get : function() { return this._meta; }
    }
});

/**
 * 生成表格数据
 * @private
 */
ExcelAsset.prototype._makeSheetData = function() {
    var sheetsName = this._data ? Object.keys(this._data) : [];
    for (var i = 0; i < sheetsName.length; i++) {
        var sheetName = sheetsName[i];
        var rows = this._data[sheetName].rows;
        var cols = this._data[sheetName].cols;
        var primaryKey = this._meta[sheetName] ? (this._meta[sheetName].primaryKey || []) : [];
        var sheetData = new qc.ExcelSheet(cols, rows, primaryKey);
        this._sheets[sheetName] = sheetData;
    }
};

/**
 * 通过名字获取一个表格的所有数据
 * @param name {string} - 表单名字
 */
ExcelAsset.prototype.findSheet = function(name) {
    return this.sheets[name];
};

/**
 * 释放excel资源
 * @param game
 * @internal
 */
ExcelAsset.prototype.unload = function(game) {
    // do nothing
};

/**
 * Excel的日期时间与javascript时间起点间的差值
 * @type {number}
 * @constant
 * @private
 */
ExcelAsset._EXCEL_DATE_OFF = -2208988800000; // new Date('1900-1-1') - new Date('1970-1-1');

/**
 * 当前时区的偏移毫秒数
 * @type {number}
 * @constant
 * @private
 */
ExcelAsset._TIMEZONE_OFF = new Date().getTimezoneOffset() * 60 * 1000;

/**
 * excel的日期类型是从1900年开始，使用时需要进行转化
 * @param number {Number} - excel的时间数值
 * @return {Date} - javascript的时间
 */
ExcelAsset.parseToDate = function(number) {
    return new Date(ExcelAsset._EXCEL_DATE_OFF + number * 86400000 + ExcelAsset._TIMEZONE_OFF);
};
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 表格类数据
 * @class qc.ExcelSheet
 * @constructor
 * @internal
 */
var ExcelSheet = qc.ExcelSheet = function(cols, rows, primaryKey) {
    // 保存相关信息
    this._cols = cols || [];
    this._rows = rows || [];

    // 普通索引数据
    this._indexes = {};
    this._primaryKey = primaryKey;
    this._buildPrimaryIndex();
};
ExcelSheet.prototype.constructor = ExcelSheet;

Object.defineProperties(ExcelSheet.prototype, {
    /**
     * @property {[string]} columns - 获取所有的列名
     * @readonly
     */
    columns : {
        get : function() { return this._cols; }
    },

    /**
     * @property {[object]} rows - 获取所有的行
     * @readonly
     */
    rows : {
        get : function() { return this._rows; }
    }
});

/**
 * 创建主索引数据
 * @private
 */
ExcelSheet.prototype._buildPrimaryIndex = function() {
    this.addSortIndex('primary', this._primaryKey);
};

/**
 * 添加一个索引数据
 * @param name {string} - 索引的别名
 * @param keys {...string} - 行排序比较的列顺序，未指明的按原有顺序
 * @return {qc.ExcelSortSheetIndex}
 */
ExcelSheet.prototype.addSortIndex = function(name) {
    var self = this;
    var keys = Array.prototype.slice.call(arguments,1);
    var index;
    if (Array.isArray(keys[0])) {
        index = new qc.ExcelSortSheetIndex(this, keys[0]);
    }
    else {
        index = new qc.ExcelSortSheetIndex(this, keys);
    }
    this._indexes[name] = index;
    return index;
};

/**
 * 使用指定列名创建一个hash索引，创建后，可以直接通过值获取数据
 * @param name {string} - 索引的别名
 * @param columnName {string} - 需要作为hash键值的列名
 * @param unique {boolean} - 键值是否唯一
 * @return {qc.ExcelHashSheetIndex}
 */
ExcelSheet.prototype.addHashIndex = function(name, columnName, unique) {
    var index = new qc.ExcelHashSheetIndex(this, columnName, unique === undefined || unique);
    this._indexes[name] = index;
    return index;
};

/**
 * 获取一个已经设定好的索引
 * @param name {string} - 索引别名
 * @return {qc.ExcelSortSheetIndex | qc.ExcelHashSheetIndex}
 */
ExcelSheet.prototype.getIndex = function(name) {
    return this._indexes[name];
};

/**
 * 获取主索引，默认为第一列的索引
 * @returns {qc.ExcelSortSheetIndex}
 */
ExcelSheet.prototype.getPrimary = function() {
    return this.getIndex('primary');
};

/**
 * 遍历查找满足条件的第一个数据
 * @param func {function} - 需要查找的条件
 * @return {number} - 找到的行号
 * @private
 */
ExcelSheet.prototype.find = function(func) {
    var rows = this._rows;
    var len = rows.length;
    var i = -1;
    while (++i < len) {
        if (func(rows[i])) {
            return i;
        }
    }
    return -1;
};

/**
 * 遍历查找满足条件的最后一个数据
 * @param func {function} - 需要查找的条件
 * @return {number} - 找到的行号
 * @private
 */
ExcelSheet.prototype.findLast = function(func) {
    var rows = this._rows;
    var i = rows.length;
    while (i-- > 0) {
        if (func(rows[i])) {
            return i;
        }
    }
    return -1;
};

/**
 * 遍历查找所有满足条件的数据
 * @param func {function} - 需要查找的条件
 * @return {[number]} - 找到的行号
 * @private
 */
ExcelSheet.prototype.matches = function(func) {
    var rows = this._rows;
    var len = rows.length;
    var i = -1;
    var ret = [];
    while (++i < len) {
        if (func(rows[i])) {
            ret.push(i);
        }
    }
    return ret;
};

/**
 * 将一列的数据转化为日期类型
 * @param column {string} - 列名
 */
ExcelSheet.prototype.parseColumnToData = function(column) {
    var rows = this._rows;
    var len = rows.length;
    var i = -1;
    var ret = [];
    while (++i < len) {
        if (column in rows[i]) {
            rows[i][column] = ExcelAsset.parseToDate(rows[i][column]);
        }
    }
    return ret;
};
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 表格类数据的排序类索引数据，数据的查找使用二分法进行
 * @class qc.ExcelSortSheetIndex
 * @param excelSheet {qc.ExcelSheet} - 表格数据
 * @param keys {[string]} - 用来排序的列名
 * @constructor
 * @internal
 */
var ExcelSortSheetIndex = qc.ExcelSortSheetIndex = function(excelSheet, keys) {
    /**
     * @property {qc.ExcelSheet} sheet - 表格数据
     */
    this.sheet = excelSheet;

    /**
     * @property {[string]} _keys - 索引的列名
     * @private
     */
    this._keys = keys;

    // 创建索引数据
    this._buildIndex();
};
ExcelSortSheetIndex.prototype = {};
ExcelSortSheetIndex.prototype.constructor = ExcelSortSheetIndex;

Object.defineProperties(ExcelSortSheetIndex.prototype, {
    /**
     * @property {[string]} columns - 存储排序的列信息
     * 优先按设定的key顺序，之后按原有columns的顺序
     * @readonly
     */
    columns : {
        get : function() { return this._cols.slice(0); }
    },

    /**
     * @property {[{}]} rows - 当前索引下的行数据
     * @readonly
     */
    rows : {
        get : function() { return this._rows; }
    }
});

/**
 * 创建索引数据
 * @private
 */
ExcelSortSheetIndex.prototype._buildIndex = function() {
    var self = this;
    var cols = this.sheet.columns.slice(0);
    if (this._keys) {
        for (var i = 0; i < this._keys.length; i++) {
            var idx = cols.indexOf(this._keys[i]);
            cols.splice(idx, 1);
        }
        Array.prototype.unshift.apply(cols, this._keys);
    }
    self._cols = cols;
    self._rows = this.sheet.rows.slice(0);
    self._rows.sort(function(one, two) {
        self._rowCompare(cols, one, two);
    });
};

/**
 * 查找第一个满足条件的行
 * @param {...*} values - 满足条件的值，值的顺序为本对象columns设定的顺序
 */
ExcelSortSheetIndex.prototype.first = function() {
    var argLen = arguments.length;
    if (argLen === 0)
        return 0;
    var self = this;
    return this._dichotomySearchFirst(this._rows, arguments, function(value, arrayElement) {
        return self._searchCompare(value, arrayElement);
    });
};

/**
 * 查找第一个满足条件的行
 * @param {...*} values - 满足条件的值，值的顺序为本对象columns设定的顺序
 */
ExcelSortSheetIndex.prototype.last = function() {
    var argLen = arguments.length;
    if (argLen === 0)
        return 0;
    var self = this;
    return this._dichotomySearchLast(this._rows, arguments, function(value, arrayElement) {
        return self._searchCompare(value, arrayElement);
    });
};

/**
 * 查找第一个满足条件的行范围
 * @param {...*} values - 满足条件的值，值的顺序为本对象columns设定的顺序
 * @return {[start, end] | null}
 */
ExcelSortSheetIndex.prototype.matches = function() {
    var argLen = arguments.length;
    if (argLen === 0)
        return [0, this._rows.length];
    var self = this;
    var first = this._dichotomySearchFirst(this._rows, arguments, function(value, arrayElement) {
        return self._searchCompare(value, arrayElement);
    });
    if (first < 0) {
        return null;
    }
    var last = this._dichotomySearchFirst(this._rows, arguments, function(value, arrayElement) {
        return self._searchCompare(value, arrayElement);
    }, first);
    return [first, last];
};

/**
 * 单元格数据比较
 * @param one {number|string|null|undefined} - 单元格数据
 * @param two {number|string|null|undefined} - 单元格数据
 * @private
 */
ExcelSortSheetIndex.prototype._cellCompare = function(one, two) {
    if (!one) {
        if (!two)
            return 0;
        else
            return 1;
    }
    if (!two)
        return -1;

    if (one === two)
        return 0;
    else if (one < two)
        return -1;
    else
        return 1;
};

/**
 * 查询时使用的比较函数
 * @param value
 * @param arrayElement
 * @private
 */
ExcelSortSheetIndex.prototype._searchCompare = function(value, arrayElement) {
    if (!arrayElement) {
        return 1;
    }
    var cols = this._cols;
    var argLen = value.length;
    var i = -1;
    var ret;
    while (++i < argLen) {
        ret = this._cellCompare(value[i], arrayElement[cols[i]]);
        if (ret > 0)
            return 1;
        else if (ret < 0)
            return -1;
    }
    return 0;
};

/**
 * 行进行比较
 * @param one {{}} - 需要比较的行数据
 * @param two {{}} - 需要比较的列数据
 * @param cols {[string]} - 需要比较的列
 * @param start {number} - 开始的位置
 * @param length {number} - 长度
 * @private
 */
ExcelSortSheetIndex.prototype._rowCompare = function(one, two, cols, start, length) {
    if (!one) {
        if (!two)
            return 0;
        else
            return 1;
    }
    if (!two)
        return -1;

    start = start || 0;
    var end = start + (length || (cols.length - start));
    var col, ret;
    for (var i = start; i < end; i++) {
        col = cols[i];
        ret = this._cellCompare(one[col], two[col]);
        if (ret === 0)
            continue;
        return ret;
    }
    return 0;
};

/**
 * 通过二分法在数组中查找相关
 * @param array {[]} - 需要查询的数组
 * @param value {*} - 需要查询的值
 * @param compare {Function} - 比较函数,function(value, arrayElement)
 * @return {number}
 */
ExcelSortSheetIndex.prototype._dichotomySearch = function(array, value, compare) {
    var low = 0;
    var high = array.length - 1;
    var middle = 0;
    var ret = 0;
    while (low <= high) {
        middle = Math.floor((low + high) / 2);
        ret = compare(value, array[middle]);
        if (ret < 0) {
            high = middle - 1;
        }
        else if (ret > 0) {
            low = middle + 1;
        }
        else {
            return middle;
        }
    }
    return -1;
};

/**
 * 数组中存在同键值的，查找第一次出现的值
 * @param array {[]} - 需要查询的数组
 * @param value {*} - 需要查询的值
 * @param compare {Function} - 比较函数,function(value, arrayElement)
 * @param low {number} - 查询开始的序号
 * @param high {number} - 查询结束的序号
 * @returns {number}
 * @private
 */
ExcelSortSheetIndex.prototype._dichotomySearchFirst = function(array, value, compare, low, high) {
    var low = low || 0;
    var high = high || array.length - 1;
    var middle = 0;
    var ret = 0;
    while (low < high) {
        middle = Math.floor((low + high) / 2);
        ret = compare(value, array[middle]);
        if (ret <= 0) {
            high = middle;
        }
        else {
            low = middle + 1;
        }
    }
    if (compare(value, array[low]) === 0) {
        return low;
    }
    return -1;
};

/**
 * 数组中存在同键值的，查找最后一次出现的值
 * @param array {[]} - 需要查询的数组
 * @param value {*} - 需要查询的值
 * @param compare {Function} - 比较函数,function(value, arrayElement)
 * @param low {number} - 查询开始的序号
 * @param high {number} - 查询结束的序号
 * @returns {number}
 * @private
 */
ExcelSortSheetIndex.prototype._dichotomySearchLast = function(array, value, compare, low, high) {
    var low = low || 0;
    var high = high || array.length - 1;
    var middle = 0;
    var ret = 0;
    while (low < high) {
        middle = Math.ceil((low + high) / 2);
        ret = compare(value, array[middle]);
        if (ret < 0) {
            high = middle - 1;
        }
        else {
            low = middle;
        }
    }
    if (compare(value, array[high]) === 0) {
        return low;
    }
    return -1;
};
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 表格类数据的hash类索引
 * @class qc.ExcelHashSheetIndex
 * @param excelSheet {qc.ExcelSheet} - 表格数据
 * @param hashKey {string} - 用来排序的列名
 * @param unique {boolean} - 键值是否唯一，唯一时，获取数据时获取的是所在行的值，否则获取的为行数组
 * @constructor
 * @internal
 */
var ExcelHashSheetIndex = qc.ExcelHashSheetIndex = function(excelSheet, hashKey, unique) {
    // 创建索引数据
    this._buildIndex(excelSheet, hashKey, unique);
};
ExcelHashSheetIndex.prototype = {};
ExcelHashSheetIndex.prototype.constructor = ExcelHashSheetIndex;

Object.defineProperties(ExcelHashSheetIndex.prototype, {
    /**
     * @property {[string]} columns - 存储排序的列信息
     * 优先按设定的key顺序，之后按原有columns的顺序
     * @readonly
     */
    columns : {
        get : function() { return this._cols.slice(0); }
    },

    /**
     * @property {[{}]} rows - 当前索引下的行数据
     * @readonly
     */
    rows : {
        get : function() { return this._rows; }
    },

    /**
     * @property {[string]} keys - 所有的键值
     * @readonly
     */
    keys : {
        get : function() { return this._keys; }
    }
});
/**
 * 创建索引数据
 * @param excelSheet {qc.ExcelSheet} - 表格数据
 * @param hashKey {string} - 用来排序的列名
 * @param unique {boolean} - 键值是否唯一，唯一时，获取数据时获取的是所在行的值，否则获取的为行数组
 * @private
 */
ExcelHashSheetIndex.prototype._buildIndex = function(excelSheet, hashKey, unique) {
    var rows = excelSheet.rows;
    this._rows = rows;
    this._cols = excelSheet.columns.slice(0);
    this._keys = [];
    var len = rows.length, i = -1;
    while (++i < len) {
        var row = rows[i];
        var keyValue = row[hashKey];
        if (unique) {
            this[keyValue] = row;
            this._keys.push(keyValue);
        }
        else {
            if (keyValue in this) {
                this[keyValue].push(row);
            }
            else {
                this[keyValue] = [row];
                this._keys.push(keyValue);
            }
        }
    }
};

/**
 * @author wudm
 * copyright 2015 Qcplay All Rights Reserved.
 */


// TODO: hack Phaser.Loader 中 fileComplete 结束回调后，上下文变化导致无法找到 game.cache 而报错
/**
 * Called when a file/resources had been downloaded and needs to be processed further.
 *
 * @method Phaser.Loader#fileComplete
 * @private
 * @param {object} file - File loaded
 * @param {?XMLHttpRequest} xhr - XHR request, unspecified if loaded via other means (eg. tags)
 */
var phaserFileComplete = Phaser.Loader.prototype.fileComplete;
Phaser.Loader.prototype.fileComplete = function (file, xhr) {
    // hack start
    // 上下文已经发生变化，不需要后续的行为
    if (!this.game.cache) return;
    // hack end

    // 返回原有函数进行继续处理
    phaserFileComplete.call(this, file, xhr);
};
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 数学库的常用接口
 *
 * @class qc.Math
 * @construct
 * @internal
 */
var Math2 = qc.Math = function(rnd) {
    this.phaserMath = Phaser.Math;
    this.arrayUtils = Phaser.ArrayUtils;
    this.phaserRnd = rnd;
}
Math2.prototype = {};
Math2.prototype.constructor = Math2;

/**
 * Two number are fuzzyEqual if their difference is less than epsilon.
 *
 * @method qc.Math#fuzzyEqual
 * @param {number} a
 * @param {number} b
 * @param {number} [epsilon=(small value)]
 * @return {boolean} True if |a-b|<epsilon
 */
Math2.prototype.fuzzyEqual = function(a, b, epsilon) {
    return this.phaserMath.fuzzyEqual(a, b, epsilon);
};

/**
 * A standard Fisher-Yates Array shuffle implementation.
 *
 * @method qc.Math#shuffle
 * @param {any[]} array - The array to shuffle.
 * @return {any[]} The shuffled array.
 */
Math2.prototype.shuffle = function (array) {
    return this.arrayUtils.shuffle(array);
};

/**
 * `a` is fuzzyLessThan `b` if it is less than b + epsilon.
 *
 * @method qc.Math#fuzzyLessThan
 * @param {number} a
 * @param {number} b
 * @param {number} [epsilon=(small value)]
 * @return {boolean} True if a<b+epsilon
 */
Math2.prototype.fuzzyLessThan = function(a, b, epsilon) {
    return this.phaserMath.fuzzyLessThan(a, b, epsilon);
}

/**
 * `a` is fuzzyGreaterThan `b` if it is more than b - epsilon.
 *
 * @method qc.Math#fuzzyGreaterThan
 * @param {number} a
 * @param {number} b
 * @param {number} [epsilon=(small value)]
 * @return {boolean} True if a>b+epsilon
 */
Math2.prototype.fuzzyGreaterThan = function(a, b, epsilon) {
    return this.phaserMath.fuzzyGreaterThan(a, b, epsilon);
}

/**
 * @method qc.Math#fuzzyCeil
 *
 * @param {number} val
 * @param {number} [epsilon=(small value)]
 * @return {boolean} ceiling(val-epsilon)
 */
Math2.prototype.fuzzyCeil = function(val, epsilon) {
    return this.phaserMath.fuzzyCeil(val, epsilon);
}

/**
 * @method qc.Math#fuzzyFloor
 *
 * @param {number} val
 * @param {number} [epsilon=(small value)]
 * @return {boolean} floor(val-epsilon)
 */
Math2.prototype.fuzzyFloor = function(val, epsilon) {
    return this.phaserMath.fuzzyFloor(val, epsilon);
}

/**
 * 计算平均数
 *
 * @method qc.Math#average
 * @params {...number} The numbers to average
 * @return {number} The average of all given values.
 */
Math2.prototype.average = function() {
    return this.phaserMath.average.apply(null, arguments);
}

/**
 * @method qc.Math#shear
 * @param {number} n
 * @return {number} n mod 1
 */
Math2.prototype.shear = function(n) {
    return this.phaserMath.shear(n);
}

/**
 * Snap a value to nearest grid slice, using rounding.
 *
 * Example: if you have an interval gap of 5 and a position of 12... you will snap to 10 whereas 14 will snap to 15.
 *
 * @method qc.Math#snapTo
 * @param {number} input - The value to snap.
 * @param {number} gap - The interval gap of the grid.
 * @param {number} [start] - Optional starting offset for gap.
 * @return {number}
 */
Math2.prototype.snapTo = function(input, gap, start) {
    return this.phaserMath.snapTo(input, gap, start);
}

/**
 * Snap a value to nearest grid slice, using floor.
 *
 * Example: if you have an interval gap of 5 and a position of 12... you will snap to 10. As will 14 snap to 10...
 * but 16 will snap to 15
 *
 * @method qc.Math#snapToFloor
 * @param {number} input - The value to snap.
 * @param {number} gap - The interval gap of the grid.
 * @param {number} [start] - Optional starting offset for gap.
 * @return {number}
 */
Math2.prototype.snapToFloor = function(input, gap, start) {
    return this.phaserMath.snapToFloor(input, gap, start);
}

/**
 * Snap a value to nearest grid slice, using ceil.
 *
 * Example: if you have an interval gap of 5 and a position of 12... you will snap to 15. As will 14 will snap to 15...
 * but 16 will snap to 20.
 *
 * @method qc.Math#snapToCeil
 * @param {number} input - The value to snap.
 * @param {number} gap - The interval gap of the grid.
 * @param {number} [start] - Optional starting offset for gap.
 * @return {number}
 */
Math2.prototype.snapToCeil = function(input, gap, start) {
    return this.phaserMath.snapToCeil(input, gap, start);
}

/**
 * Round to some place comparative to a `base`, default is 10 for decimal place.
 * The `place` is represented by the power applied to `base` to get that place.
 *
 *     e.g. 2000/7 ~= 285.714285714285714285714 ~= (bin)100011101.1011011011011011
 *
 *     roundTo(2000/7,3) === 0
 *     roundTo(2000/7,2) == 300
 *     roundTo(2000/7,1) == 290
 *     roundTo(2000/7,0) == 286
 *     roundTo(2000/7,-1) == 285.7
 *     roundTo(2000/7,-2) == 285.71
 *     roundTo(2000/7,-3) == 285.714
 *     roundTo(2000/7,-4) == 285.7143
 *     roundTo(2000/7,-5) == 285.71429
 *
 *     roundTo(2000/7,3,2)  == 288       -- 100100000
 *     roundTo(2000/7,2,2)  == 284       -- 100011100
 *     roundTo(2000/7,1,2)  == 286       -- 100011110
 *     roundTo(2000/7,0,2)  == 286       -- 100011110
 *     roundTo(2000/7,-1,2) == 285.5     -- 100011101.1
 *     roundTo(2000/7,-2,2) == 285.75    -- 100011101.11
 *     roundTo(2000/7,-3,2) == 285.75    -- 100011101.11
 *     roundTo(2000/7,-4,2) == 285.6875  -- 100011101.1011
 *     roundTo(2000/7,-5,2) == 285.71875 -- 100011101.10111
 *
 * Note what occurs when we round to the 3rd space (8ths place), 100100000, this is to be assumed
 * because we are rounding 100011.1011011011011011 which rounds up.
 *
 * @method qc.Math#roundTo
 * @param {number} value - The value to round.
 * @param {number} place - The place to round to.
 * @param {number} base - The base to round in... default is 10 for decimal.
 * @return {number}
 */
Math2.prototype.roundTo = function(value, place, base) {
    return this.phaserMath.roundTo(value, place, base);
}

/**
 * @method qc.Math#floorTo
 * @param {number} value - The value to round.
 * @param {number} place - The place to round to.
 * @param {number} base - The base to round in... default is 10 for decimal.
 * @return {number}
 */
Math2.prototype.floorTo = function(value, place, base) {
    return this.phaserMath.floorTo(value, place, base);
}

/**
 * @method qc.Math#ceilTo
 * @param {number} value - The value to round.
 * @param {number} place - The place to round to.
 * @param {number} base - The base to round in... default is 10 for decimal.
 * @return {number}
 */
Math2.prototype.ceilTo = function(value, place, base) {
    return this.phaserMath.ceilTo(value, place, base);
}

/**
 * Find the angle of a segment from (x1, y1) -> (x2, y2).
 * @method qc.Math#angleBetween
 * @param {number} x1
 * @param {number} y1
 * @param {number} x2
 * @param {number} y2
 * @return {number} The angle, in radians.
 */
Math2.prototype.angleBetween = function(x1, y1, x2, y2) {
    return this.phaserMath.angleBetween(x1, y1, x2, y2);
}

/**
 * Find the angle of a segment from (x1, y1) -> (x2, y2).
 * Note that the difference between this method and Math.angleBetween is that this assumes the y coordinate travels
 * down the screen.
 *
 * @method qc.Math#angleBetweenY
 * @param {number} x1
 * @param {number} y1
 * @param {number} x2
 * @param {number} y2
 * @return {number} The angle, in radians.
 */
Math2.prototype.angleBetweenY = function(x1, y1, x2, y2) {
    return this.phaserMath.angleBetweenY(x1, y1, x2, y2);
}

/**
 * Reverses an angle.
 * @method qc.Math#reverseAngle
 * @param {number} angleRad - The angle to reverse, in radians.
 * @return {number} Returns the reverse angle, in radians.
 */
Math2.prototype.reverseAngle = function(angleRad) {
    return this.phaserMath.reverseAngle(angleRad);
}

/**
 * Normalizes an angle to the [0,2pi) range.
 * @method qc.Math#normalizeAngle
 * @param {number} angleRad - The angle to normalize, in radians.
 * @return {number} Returns the angle, fit within the [0,2pi] range, in radians.
 */
Math2.prototype.normalizeAngle = function(angleRad) {
    return this.phaserMath.normalizeAngle(angleRad);
}

/**
 * Adds the given amount to the value, but never lets the value go over the specified maximum.
 *
 * @method qc.Math#maxAdd
 * @param {number} value - The value to add the amount to.
 * @param {number} amount - The amount to add to the value.
 * @param {number} max - The maximum the value is allowed to be.
 * @return {number}
 */
Math2.prototype.maxAdd = function(value, amount, max) {
    return this.phaserMath.maxAdd(value, amount, max);
}

/**
 * Subtracts the given amount from the value, but never lets the value go below the specified minimum.
 *
 * @method qc.Math#minSub
 * @param {number} value - The base value.
 * @param {number} amount - The amount to subtract from the base value.
 * @param {number} min - The minimum the value is allowed to be.
 * @return {number} The new value.
 */
Math2.prototype.minSub = function(value, amount, min) {
    return this.phaserMath.minSub(value, amount, min);
}

/**
 * Ensures that the value always stays between min and max, by wrapping the value around.
 *
 * @method qc.Math#wrap
 * @param {number} value - The value to wrap.
 * @param {number} min - The minimum the value is allowed to be.
 * @param {number} max - The maximum the value is allowed to be, should be larger than `min`.
 * @return {number} The wrapped value.
 */
Math2.prototype.wrap = function(value, min, max) {
    if (min < max)
        return this.phaserMath.wrap(value, min, max);
    return this.phaserMath.wrap(value, max, min);
}

/**
 * Adds value to amount and ensures that the result always stays between 0 and max, by wrapping the value around.
 *
 * Values _must_ be positive integers, and are passed through Math.abs. See {@link qc.Math#wrap} for an alternative.
 *
 * @method qc.Math#wrapValue
 * @param {number} value - The value to add the amount to.
 * @param {number} amount - The amount to add to the value.
 * @param {number} max - The maximum the value is allowed to be.
 * @return {number} The wrapped value.
 */
Math2.prototype.wrapValue = function(value, amount, max) {
    return this.phaserMath.wrapValue(value, amount, max);
}

/**
 * Returns true if the number given is odd.
 *
 * @method qc.Math#isOdd
 * @param {integer} n - The number to check.
 * @return {boolean} True if the given number is odd. False if the given number is even.
 */
Math2.prototype.isOdd = function(n) {
    return !!(this.phaserMath.isOdd(n));
}

/**
 * Returns true if the number given is even.
 *
 * @method qc.Math#isEven
 * @param {integer} n - The number to check.
 * @return {boolean} True if the given number is even. False if the given number is odd.
 */
Math2.prototype.isEven = function(n) {
    return !!(this.phaserMath.isEven(n));
}

/**
 * Variation of Math.min that can be passed either an array of numbers or the numbers as parameters.
 *
 * Prefer the standard `Math.min` function when appropriate.
 *
 * @method qc.Math#min
 * @return {number} The lowest value from those given.
 * @see {@link http://jsperf.com/math-s-min-max-vs-homemade}
 */
Math2.prototype.min = function() {
    return this.phaserMath.min.apply(null, arguments);
}

/**
 * Variation of Math.max that can be passed either an array of numbers or the numbers as parameters.
 *
 * Prefer the standard `Math.max` function when appropriate.
 *
 * @method qc.Math#max
 * @return {number} The largest value from those given.
 * @see {@link http://jsperf.com/math-s-min-max-vs-homemade}
 */
Math2.prototype.max = function() {
    return this.phaserMath.max.apply(null, arguments);
}

/**
 * Keeps an angle value between -180 and +180; or -PI and PI if radians.
 *
 * @method qc.Math#wrapAngle
 * @param {number} angle - The angle value to wrap
 * @param {boolean} [radians=false] - Set to `true` if the angle is given in radians, otherwise degrees is expected.
 * @return {number} The new angle value; will be the same as the input angle if it was within bounds.
 */
Math2.prototype.wrapAngle = function(angle, radians) {
    return this.phaserMath.wrapAngle(angle, radians);
}

/**
 * Calculates a linear (interpolation) value over t.
 *
 * @method qc.Math#linear
 * @param {number} p0
 * @param {number} p1
 * @param {number} t
 * @return {number}
 */
Math2.prototype.linear = function(p0, p1, t) {
    return this.phaserMath.linear(p0, p1, t);
}

/**
 * @method qc.Math#factorial
 * @param {number} value - the number you want to evaluate
 * @return {number}
 */
Math2.prototype.factorial = function(value) {
    return this.phaserMath.factorial(value);
}

/**
 * Fetch a random entry from the given array.
 *
 * Will return null if there are no array items that fall within the specified range
 * or if there is no item for the randomly choosen index.
 *
 * @method qc.Math#getRandom
 * @param {any[]} objects - An array of objects.
 * @param {integer} startIndex - Optional offset off the front of the array. Default value is 0, or the beginning of the array.
 * @param {integer} length - Optional restriction on the number of values you want to randomly select from.
 * @return {object} The random object that was selected.
 */
Math2.prototype.getRandom = function(objects, startIndex, length) {
    return Phaser.ArrayUtils.getRandomItem(objects, startIndex, length);
}

/**
 * 计算两个点之间的距离
 *
 * @method qc.Math#distance
 * @param {number} x1
 * @param {number} y1
 * @param {number} x2
 * @param {number} y2
 * @return {number} The distance between the two sets of coordinates.
 */
Math2.prototype.distance = function(x1, y1, x2, y2) {
    return this.phaserMath.distance(x1, y1, x2, y2);
}

/**
 * Force a value within the boundaries by clamping `x` to the range `[a, b]`.
 *
 * @method qc.Math#clamp
 * @param {number} x
 * @param {number} a
 * @param {number} b
 * @return {number}
 */
Math2.prototype.clamp = function(x, a, b) {
    return this.phaserMath.clamp(x, a, b);
}

/**
 * Checks if two values are within the given tolerance of each other.
 *
 * @method qc.Math#within
 * @param {number} a - The first number to check
 * @param {number} b - The second number to check
 * @param {number} tolerance - The tolerance. Anything equal to or less than this is considered within the range.
 * @return {boolean} True if a is <= tolerance of b.
 * @see {@link Phaser.Math.fuzzyEqual}
 */
Math2.prototype.within = function(a, b, tolerance) {
    return this.phaserMath.within(a, b, tolerance);
}

/**
 * A value representing the sign of the value: -1 for negative, +1 for positive, 0 if value is 0.
 *
 * This works differently from `Math.sign` for values of NaN and -0, etc.
 *
 * @method qc.Math#sign
 * @param {number} x
 * @return {integer} An integer in {-1, 0, 1}
 */
Math2.prototype.sign = function(x) {
    return this.phaserMath.sign(x);
}

/**
 * Convert degrees to radians.
 *
 * @method qc.Math#degToRad
 * @param {number} degrees - Angle in degrees.
 * @return {number} Angle in radians.
 */
Math2.prototype.degToRad = function(degrees) {
    return this.phaserMath.degToRad(degrees);
}

/**
 * Convert degrees to radians.
 *
 * @method qc.Math#radToDeg
 * @param {number} radians - Angle in radians.
 * @return {number} Angle in degrees
 */
Math2.prototype.radToDeg = function(radians) {
    return this.phaserMath.radToDeg(radians);
}

/**
 * Returns a random real number between 0 and 1.(未指定最大最小值)
 * 否则返回[min, max]直接的一个随机整数
 *
 * @method qc.Math#frac
 * @return {number} A random real number between 0 and 1.
 */
Math2.prototype.random = function(min, max) {
    if (typeof min === "undefined" || typeof max === "undefined")
        return this.phaserRnd.frac();

    if (min > max)
        return this.phaserRnd.integerInRange(max, min);
    return this.phaserRnd.integerInRange(min, max);
}

/**
 * 返回一个唯一字符串
 *
 * @method qc.Math#uuid
 */
Math2.prototype.uuid = function() {
    return this.phaserRnd.uuid();
}

/**
 * 返回下一个全局id值
 */
Math2.prototype.id = (function(){
    var id = 1;
    return function(){
        return ++id;
    };
})();

// 计算逆矩阵
Math2.prototype.invert = function(out, a) {
    var a00 = a[0], a01 = a[1], a02 = a[2],
        a10 = a[3], a11 = a[4], a12 = a[5],
        a20 = a[6], a21 = a[7], a22 = a[8],

        b01 = a22 * a11 - a12 * a21,
        b11 = -a22 * a10 + a12 * a20,
        b21 = a21 * a10 - a11 * a20,

        det = a00 * b01 + a01 * b11 + a02 * b21;

    if (!det) {
        return null;
    }
    det = 1.0 / det;

    out[0] = b01 * det;
    out[1] = (-a22 * a01 + a02 * a21) * det;
    out[2] = (a12 * a01 - a02 * a11) * det;
    out[3] = b11 * det;
    out[4] = (a22 * a00 - a02 * a20) * det;
    out[5] = (-a12 * a00 + a02 * a10) * det;
    out[6] = b21 * det;
    out[7] = (-a21 * a00 + a01 * a20) * det;
    out[8] = (a11 * a00 - a01 * a10) * det;
    return out;
};

// 矩阵乘积
Math2.prototype.multiply = function (out, a, b) {
    var a00 = a[0], a01 = a[1], a02 = a[2],
        a10 = a[3], a11 = a[4], a12 = a[5],
        a20 = a[6], a21 = a[7], a22 = a[8],

        b00 = b[0], b01 = b[1], b02 = b[2],
        b10 = b[3], b11 = b[4], b12 = b[5],
        b20 = b[6], b21 = b[7], b22 = b[8];

    out[0] = b00 * a00 + b01 * a10 + b02 * a20;
    out[1] = b00 * a01 + b01 * a11 + b02 * a21;
    out[2] = b00 * a02 + b01 * a12 + b02 * a22;

    out[3] = b10 * a00 + b11 * a10 + b12 * a20;
    out[4] = b10 * a01 + b11 * a11 + b12 * a21;
    out[5] = b10 * a02 + b11 * a12 + b12 * a22;

    out[6] = b20 * a00 + b21 * a10 + b22 * a20;
    out[7] = b20 * a01 + b21 * a11 + b22 * a21;
    out[8] = b20 * a02 + b21 * a12 + b22 * a22;
    return out;
};

// 平滑阻尼
Math2.prototype.smoothDamp = function (current, target, currentVelocity, smoothTime, maxSpeed, deltaTime) {
    smoothTime = Math.max(0.0001, smoothTime);
    var num = 2 / smoothTime;
    var num2 = num * deltaTime;
    var num3 = 1 / ( 1 + num2 + 0.48 * num2 * num2 + 0.235 * num2 * num2 * num2);
    var num4 = current - target;
    var num5 = target;
    var num6 = maxSpeed * smoothTime;
    num4 = this.clamp(num4, -num6, num6);
    target = current - num4;
    var num7 = (currentVelocity + num * num4) * deltaTime;
    currentVelocity = (currentVelocity - num * num7) * num3;
    var num8 = target + (num4 + num7) * num3;
    if (num5 - current > 0 && num8 > num5) {
        num8 = num5;
        currentVelocity = (num8 - num5) / deltaTime;
    }
    return [num8, currentVelocity];
};

/**
 * @author wudm
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 负责处理PIXI renderer 中 spriteBatch 对 sprite 的失效引用
 * 该引用会导致内存泄漏
 * @param game
 * @param parent
 * @constructor
 * @internal
 */

qc.CleanPIXISpriteRetainer = function(game, parent) {
    this._frameCount = 0;
    this.game = game;
}
qc.CleanPIXISpriteRetainer.prototype = {
    postRender : function() {
        this._frameCount++;
        if (this._frameCount < 100)
        // 100 帧再统一处理一次
            return;

        this._frameCount = 0;
        var renderer = this.game.phaser.renderer;
        if (!(renderer instanceof PIXI.WebGLRenderer))
        // 非 WebGL 模式不处理
            return;

        // 执行具体的消除引用行为
        var sprites = renderer.spriteBatch.sprites;
        var batchSize = renderer.spriteBatch.currentBatchSize;
        var maxLen = sprites.length;
        for (var i = batchSize; i < maxLen; i++) {
            sprites[i] = null;
        }
    }
};
qc.CleanPIXISpriteRetainer.prototype.constructor = qc.CleanPIXISpriteRetainer;

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 游戏对象池管理
 *
 * @class qc.NodePool
 * @constructor
 * @internal
 */
var NodePool = qc.NodePool = function(game) {
    this._game = game;
    this._nodes = {};
}
NodePool.prototype = {};
NodePool.prototype.constructor = NodePool;

Object.defineProperties(NodePool.prototype, {
    /**
     * @property {qc.Game} game - 游戏实例的引用
     * @readonly
     */
    game : {
        get : function() { return this._game; }
    }
});

/**
 * 添加一个对象
 * @method qc.NodePool#add
 */
NodePool.prototype.add = function(uuid, node) {
    if (this._nodes[uuid]) {
        this.game.log.error('uuid {0} already exists', uuid);
    }
    this._nodes[uuid] = node;
}

/**
 * 移除一个对象
 * @method qc.NodePool@remove
 */
NodePool.prototype.remove = function(uuid) {
    if (this._nodes[uuid])
        delete this._nodes[uuid];
}

/**
 * 查找对象
 * @method qc.NodePool#find
 */
NodePool.prototype.find = function(uuid) {
    if (this._nodes[uuid])
        return this._nodes[uuid];
}
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 所有的游戏逻辑脚本都应该继承此类，然后挂载到相应的场景对象中以起作用。
 * 继承此类的对象，构造时都应该使用qc.Behaviour#add接口创建，不能直接new
 *
 * @class qc.Behaviour
 * @constructor
 */
var Behaviour = qc.Behaviour = function(gameObject) {
    this._gameObject = gameObject;

    // 缓存本节点关注的事件
    this._events = [];
};
Behaviour.prototype.constructor = Behaviour;

/**
 * 定义一个逻辑脚本
 * @method defineBehaviour
 */
qc._userScripts = {};
var defineBehaviour = qc.defineBehaviour = function(clazz, parent, init, fields) {
    // 先将clazz分解下，并创建包
    var arr = clazz.split('.');
    var curr = window;
    for (var i = 0; i < arr.length - 1; i++) {
        if (!curr[arr[i]]) curr[arr[i]] = {};
        curr = curr[arr[i]];
    }
    var name = arr[arr.length - 1];

    parent = parent || qc.Behaviour;
    if (typeof fields === 'function') {
        // 指定了函数，则调用下取返回值
        fields = fields.call(this);
    }

    fields = fields || {};
    curr[name] = function(gameObject) {
        // 记录下是否有调度函数
        this.__hadUpdateOrRender = (this.preUpdate || this.update || this.postUpdate) ? true : false;

        // 记录父亲和类名
        parent.call(this, gameObject);
        this.class = clazz;
        this.super = parent.prototype;

        // 调用初始化函数
        if (typeof init === 'function')
            init.call(this);
    };
    curr[name].prototype = Object.create(parent.prototype);
    curr[name].prototype.constructor = curr[name];

    // 设置需要序列化的字段
    curr[name].prototype.getMeta = function() {
        var meta = parent.prototype.getMeta.call(this);

        // 合并下
        return mixin(meta, fields);
    };

    curr[name].prototype.clazz = clazz;

    qc._userScripts[clazz] = curr[name];
    return curr[name];
};

Object.defineProperties(Behaviour.prototype, {
    /**
     * @property {string} uuid - 脚本的唯一标识
     * @readonly
     */
    uuid: {
        get: function()  { return this._uuid; },
        set: function(v) { this._uuid = v;    }
    },
    
    /**
     * 取得节点的对象
     * @property {qc.Behaviour}
     * @readonly
     */
    "gameObject" : {
        get : function() { return this._gameObject; }
    },

    /**
     * 节点的名字
     * @property {string}
     */
    'name' : {
        get : function()  { return this.gameObject.name; },
        set : function(v) { this.gameObject.name = v;    }
    },

    /**
     * 组件是不是激活的，如果没有激活相当于没有add此脚本
     */
    enable : {
        get : function() { return this._enable; },
        set : function(v) {
            if (this._enable !== v) {
                this._enable = v;
                if (this._enable && this.onEnable) {
                    this.onEnable();
                }
                else if (!this._enable && this.onDisable) {
                    this.onDisable();
                }
            }
        }
    },

    /**
     * @property {boolean} runInEditor - 是否在编辑器模式下运行
     * @default false
     */
    runInEditor : {
        get : function()  { return this._runInEditor === true; },
        set : function(v) { this._runInEditor = v;             }
    },

    /**
     * @property {qc.Game} game - 指向的游戏实例
     */
    game : {
        get : function() { return this.gameObject.game; }
    }
});

/**
 * 取得组件
 * * @method getScript
 * @param script - 组件类名
 */
Behaviour.prototype.getScript = function(script) {
    return this.gameObject.getScript(script);
};

/**
 * 取得组件
 * * @method getScripts
 * @param script - 组件类名
 */
Behaviour.prototype.getScripts = function(script) {
    return this.gameObject.getScripts(script);
};

/**
 * 将本组件移除掉
 * @method destroy
 */
Behaviour.prototype.destroy = function() {
    this.gameObject.removeScript(this);

    // 调用回调
    if (this.onDestroy)
        this.onDestroy();

    // 移除事件关注
    this._events.forEach(function(eventData) {
        eventData[0].remove(eventData[1], eventData[2]);
    });
    this._events = [];
};

/**
 * 是不是启用
 * @method isEnable
 */
Behaviour.prototype.isEnable = function() {
    return this._enable;
};

/**
 * 需要序列化的字段信息
 * @internal
 */
Behaviour.prototype.getMeta = function() {
    return {
        uuid: qc.Serializer.STRING,
        enable: {
            get : function(ob) {
                return ob.enable;
            },
            set : function(context, v) {
                context._enable = v;
            }
        }
    };
};

/**
 * 序列化完毕后的处理工作
 * @internal
 */
Behaviour.prototype._restoreInit = function() {
    this.gameObject._initGetSetField.call(this);
};

/**
 * 使用该增加事件关注，在 destroy 时会自动移除事件关注，避免事件没被移除，导致对象被引用，内存泄漏
 * @public
 * @chenx 2015.11.11
 */
Behaviour.prototype.addListener = function(signal, listener, listenerContext, priority) {

    // 注册事件
    signal.add(listener, listenerContext, priority);

    // 加入缓存
    this._events.push([signal, listener, listenerContext]);
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 本地数据保存
 *
 * @class qc.Storage
 * @constructor
 * @internal
 */
var Storage = qc.Storage = function(game) {
    this.game = game;
    this.restore();
}
Storage.prototype = {};
Storage.prototype.constructor = Storage;

Object.defineProperties(Storage.prototype, {
    /**
     * 本游戏使用的唯一标识符，采用BundleIdentifier
     */
    key : {
        get : function() {
            return this.game.bundleIdentifier;
        }
    }
});

/**
 * 还原出所有的数据
 */
Storage.prototype.restore = function() {
    var str = window.localStorage.getItem(this.key);
    if (str) {
        this._data = JSON.parse(str);
    }
    else {
        this._data = {};
    }
}

/**
 * 保存所有数据
 */
Storage.prototype.save = function() {
    var key = this.key;
    if (!key || key === 'com.DefaultCompany.Default') {
        throw new Error('game.bundleIdentifier should be set for local storage');
    }
    var str = JSON.stringify(this._data);
    window.localStorage.setItem(this.key, str);
}

/**
 * 保存一条记录
 */
Storage.prototype.set = function(k, v) {
    this._data[k] = v;
}

/**
 * 删除一条记录
 */
Storage.prototype.del = function(k) {
    if (this._data[k])
        delete this._data[k];
}

/**
 * 检索一条记录
 */
Storage.prototype.get = function(k) {
    if (this._data[k])
        return this._data[k];
}

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 所有的DisplayObject节点都可以挂载游戏逻辑脚本。
 * Node负责动态挂载用户的逻辑脚本，所有的GameObject都应该继承此类
 *
 * @class qc.Node
 * @constructor
 */
var Node = qc.Node = function(phaser, parent, uuid) {
    var self = this;

    // 设置关联的game对象
    self.game = phaser.game._qc;

    // 关联的phaser对象
    self.phaser = phaser;
    phaser._qc = this;

    // 设置全局唯一标示
    self.uuid = uuid || self.game.math.uuid();

    // 是否允许交互，默认为false，控制onDown/onUp/onClick等事件
    self._interactive = false;

    // 是否允许拖拽，默认为false，控制onDrag事件
    self._draggable = false;

    // 缓存qc封装的孩子节点数组，在get函数内部会进行初始化构建
    self._children = null;

    // 默认不是预制
    self._prefab = '';

    /**
     * @property {Array} scripts - 所有挂载的脚本
     */
    self.scripts = [];

    /**
     * @property {boolean} static - 本节点以下的元素都是静态的，不需要执行update等逻辑调度（有助于提升效率）
     * @default: false
     */
    self.static = false;

    // 默认情况下，碰撞盒和对象的大小保持一致
    self.hitArea = undefined;

    // 缓存本节点关注的事件
    self._events = [];

    // @hackpp 重载Phaser对象的destroy函数，做我们自己的销毁工作
    var oldDestroy = phaser.destroy;
    phaser.destroy = function() {
        // 已经销毁过了，不要再次处理
        if (self._destroy) return;

        if (self === self.game.world) {
            // Phaser.Group.prototype.destroy = function (destroyChildren, soft)
            // 设置soft为true，保留world在stage不被删除
            oldDestroy.call(phaser, true, true);

            // 派发销毁事件
            self._dispatchModelChangeEvent('destroy', self);
        }
        else {
            // 销毁我自己的处理
            self.onDestroy();

            // 调用Phaser默认销毁实现，Phaser会递归调用孩子的销毁
            oldDestroy.call(phaser);

            // 通知所有挂载的脚本移除
            var i = self.scripts.length;
            while (i--) {
                self.scripts[i].destroy();
            }
            self.scripts = [];

            // 派发销毁事件
            self._dispatchModelChangeEvent('destroy', self);

            // 标记我被销毁了
            self._destroy = true;
        }
    };

    // 挂载父节点（不指定的时候默认挂在 world 上）
    // 注意：world 对象本身也继承自 node，但我们不希望 world 被挂在任何节点上。
    //      qc.world 本身是对 phaser.world 的引用，在 Phaser 那一层已经被 add 到
    //      stage 对象上
    if (parent !== this) {
        (parent || this.game.world).addChild(this);
    }

    // 设置其默认位置
    this._defaultTransform();
};
Node.prototype.constructor = Node;

Object.defineProperties(Node.prototype, {
    /**
     * @property {string} uuid - 对象的唯一标识符
     * @internal
     */
    uuid : {
        get : function() { return this._uuid; },
        set : function(v) {
            // 先干掉旧的映射关系
            if (this._uuid) {
                this.game.nodePool.remove(this.uuid);
            }

            // 建立新的关系
            this._uuid = v;
            this.game.nodePool.add(this.uuid, this);
        }
    },

    /**
     * 节点名称
     * @proeprty name
     * @type string
     */
    'name': {
        get: function() {
            return this.phaser.name;
        },
        set: function(v) {
            // 不允许出现反斜杠
            v = v || '';
            v = v.replace(/\//g, '');
            this.phaser.name = v;

            // 派发名字变化事件
            this._dispatchModelChangeEvent('name', this);
        }
    },

    /**
     * @property {boolean} ignoreDestroy - 节点及其孩子在切换场景时不会被析构
     */
    ignoreDestroy: {
        get: function()  { return this.phaser.ignoreDestroy; },
        set: function(v) { this.phaser.ignoreDestroy = v;    }
    },

    /**
     * The opacity of the object.
     *
     * @property alpha
     * @type Number
     */
    'alpha': {
        get: function() {
            return this.phaser.alpha;
        },
        set: function(v) {
            this.phaser.alpha = v;
        }
    },

    /**
     * The visibility of the object.
     *
     * @property visible
     * @type Boolean
     */
    'visible': {
        get: function() {
            return this.phaser.visible;
        },
        set: function(v) {
            if (this.phaser.visible === v) return;
            if (v && this._toDestroy) return;
            this.phaser.visible = v;
            // 需要更新变换矩阵
            this._isTransformDirty = true;
            this._dispatchLayoutArgumentChanged('size');

            // 通知孩子
            this._notifyVisibleChanged(v);
        }
    },

    /**
     * The display object container that contains this display object.
     *
     * @property parent
     * @type qc.Node
     */
    'parent': {
        get: function() {
            var parent = this.phaser.parent;
            return parent ? parent._qc : null;
        },
        set: function(parent) {
            // 父亲不为空将孩子添加到末尾
            if (parent) {
                parent.addChild(this);
            }
            // 父亲为空将孩子从老父亲中删除
            else {
                if (this.parent instanceof Node) {
                    this.parent.removeChild(this);
                }
            }
        }
    },

    /**
     * [read-only] Indicates if the sprite is globally visible.
     *
     * @property worldVisible
     * @type Boolean
     */
    worldVisible: {
        get: function() {
            return this.phaser && this.phaser.worldVisible;
        }
    },

    /**
     * Sets a mask for the displayObject. A mask is an object that limits the visibility of an object to the shape of the mask applied to it.
     * In PIXI a regular mask must be a PIXI.Graphics object. This allows for much faster masking in canvas as it utilises shape clipping.
     * To remove a mask, set this property to null.
     *
     * @property mask
     * @type Graphics
     */
    'mask': {
        get: function() {
            return this.phaser.mask;
        },
        set: function(v) {
            this.phaser.mask = v;
        }
    },

    /**
     * 所有的孩子
     *
     * @property children
     * @type Array[]
     * @readonly
     */
    'children': {
        get: function() {
            if (!this._children) {
                this._children = [];
                var list = this.phaser.children;
                for (var i = 0; i < list.length; i++) {
                    var qc = list[i]._qc;
                    // BitmapFont和Emitter类型的children是Phaser自动生成的Sprite对象，
                    // qc对这些children对象并未封装Node的绑定
                    if (qc) {
                        this._children.push(qc);
                    }
                }
            }
            return this._children;
        }
    },

    /**
     * 设置颜色混合
     * @property {qc.Color} colorTint
     */
    colorTint: {
        get: function() {
            return new Color(this.phaser.tint);
        },
        set: function(value) {
            value = value || new Color(0xFFFFFF);
            if (!(value instanceof Color)) {
                console.error(value);
                this.game.log.error('Expected qc.Color');
                return;
            }

            this.phaser.tint = value.toNumber();
        }
    },

    /**
     * 子节点变化时的通知事件
     * @property {Phaser.Signal} onChildrenChanged
     */
    onChildrenChanged: {
        get : function() {
            return this._onChildrenChanged || (this._onChildrenChanged = new Phaser.Signal());
        }
    }
});

/**
 * 是否可见
 * @method isVisible
 */
Node.prototype.isVisible = function() {
    return this.phaser.visible;
};

/**
 * 是否可见
 * @method isWorldVisible
 */
Node.prototype.isWorldVisible = function() {
    var item = this;
    do
    {
        if (!item.phaser.visible) return false;
        item = item.parent;
    }
    while(item);
    return true;
};

/**
 * 添加一个逻辑脚本对象，此脚本对象应该继承qc.Scriptable
 * @method addScript
 * @param {string} script - 挂载的逻辑脚本名称
 */
Node.prototype.addScript = function(script, dispatchAwake) {
    var clazz = qc.Util.findClass(script);
    if (typeof clazz !== 'function') {
        this.game.log.error('Class:{0} not exists', script);
        return;
    }

    // 检查此类是不是继承自Behaviour
    var c = new clazz(this);
    if (!(c instanceof qc.Behaviour)) {
        this.game.log.error("Must inherit from qc.Behaviour");
        return;
    }

    // 挂载时立刻调用其awake函数
    c.uuid = this.game.math.uuid();
    c._clazz = script;
    this.scripts.push(c);
    if (dispatchAwake || dispatchAwake === undefined) {
        // 在反序列化的时候，我们不要在addScript中派发事件
        // 如果处于editor模式，并且此脚本没有声明在编辑器模式下运行，就不要调用awake了
        if (c.awake && (this.game.device.editor !== true || c.runInEditor === true))
            c.awake();
    }
    if (c._enable === undefined &&
        dispatchAwake !== false) {
        // 没指定 enable，且不是反序列化回来的，我们默认 enable 为 true
        if (this.game.device.editor === true && c.runInEditor !== true)
            // 编辑器模式，且没指定 runInEditor，只能设置 _enable 为 true，不能调度 onEnabled
            c._enable = true;
        else
            c.enable = true;
    }

    return c;
};

/**
 * 移除一个逻辑脚本对象，使用时请调用Scriptable#destroy接口，不要直接使用本接口来移除
 * @method
 * @internal
 * @param {qc.Behaviour} script - 移除的脚本对象
 */
Node.prototype.removeScript = function(script) {
    var index = this.scripts.indexOf(script);
    if (index > -1)
        this.scripts.splice(index, 1);
};

/**
 * 取得一个逻辑脚本对象
 * @method getScript
 * @param {qc.Behaviour|string} script
 * @param {boolean} inherit - 是否判断继承关系，默认为 true
 */
Node.prototype.getScript = function(script, inherit) {
    var clazz = script;
    if (typeof script === 'string')
        clazz = qc.Util.findClass(script);
    if (!clazz) {
        this.game.log.error('Class:{0} not exists', script);
        return null;
    }
    if (arguments.length === 1 || inherit) {
        for (var c in this.scripts) {
            if (this.scripts[c] instanceof clazz) {
                return this.scripts[c];
            }
        }
    }
    else {
        for (var c in this.scripts) {
            if (this.scripts[c].class === clazz.prototype.clazz) {
                return this.scripts[c];
            }
        }
    }
};

/**
 * 取得一系列逻辑脚本对象
 * @method getScripts
 * @param {qc.Behaviour|string} script
 * @param {boolean} inherit - 是否判断继承关系，默认为 true
 * @return {[qc.Behaviour]}
 */
Node.prototype.getScripts = function(script, inherit) {
    var clazz = script;
    if (typeof script === 'string')
        clazz = qc.Util.findClass(script);
    var ret = [];
    if (!clazz) {
        this.game.log.error('Class:{0} not exists', script);
        return ret;
    }
    if (arguments.length === 1 || inherit) {
        for (var c in this.scripts) {
            if (this.scripts[c] instanceof clazz) {
                ret.push(this.scripts[c]);
            }
        }
    }
    else {
        for (var c in this.scripts) {
            if (this.scripts[c].class === clazz.prototype.clazz) {
                ret.push(this.scripts[c]);
            }
        }
    }
    return ret;
};

/**
 * 判断本对象是否为指定Node的子孙节点
 * @param node 指定的Node对象
 * @returns {boolean} 为子孙返回true，否则false
 */
Node.prototype.isDescendantOf = function(node) {
    var tempParent = this.parent;
    while (tempParent) {
        if (node === tempParent) {
            return true;
        } else {
            tempParent = tempParent.parent;
        }
    }
    return false;
};

/**
 * 取得所有的孩子
 * @method qc.Node#getChildren
 */
Node.prototype.getChildren = function() {
    var children = this._children;
    if (!children) {
        children = this.children;
    }
    return children;
};

/**
 * 添加一个孩子节点到尾部
 *
 * @method addChild
 * @param child {qc.Node} 待添加的孩子节点
 * @return {qc.Node} 被添加进来后的节点
 */
Node.prototype.addChild = function(child) {
    return this.addChildAt(child, this.children.length);
};

/**
 * 添加一个孩子到指定的位置
 *
 * @method addChildAt
 * @param child {qc.Node} 待添加的孩子节点
 * @param index {Number} 位置
 * @return {qc.Node} 添加成功后的节点
 */
Node.prototype.addChildAt = function(child, index) {
    if (child.parent === this) {
        // 没有发生什么变化，不需要做处理
        return;
    }

    // 检查下合法性
    if (!(child instanceof Node)) {
        throw new Error('Expected qc.Node');
    }
    if (child === this) {
        throw new Error('The child is myself');
    }
    if (this.isDescendantOf(child)) {
        throw new Error('child is my parent');
    }

    // 无效以前父亲的孩子缓冲
    var parent = child.parent;
    if (parent instanceof Node) {
        parent._children = null;
    }

    // Phaser.Group类型采用addAt
    if (this.phaser.addAt) {
        this.phaser.addAt(child.phaser, index);
    }
    else {
        this.phaser.addChildAt(child.phaser, index);
    }

    // 派发孩子变化事件
    this._dispatchModelChangeEvent('parent', child);
    return child;
};

/**
 * 交换两个孩子的位置
 *
 * @method swapChildren
 * @param child1
 * @param child2
 */
Node.prototype.swapChildren = function(child1, child2) {
    this.phaser.swapChildren(child1.phaser, child2.phaser);
    // 派发孩子变化事件
    this._dispatchModelChangeEvent('parent', child1);
    this._dispatchModelChangeEvent('parent', child2);
};

/**
 * 取得孩子的位置
 *
 * @method getChildIndex
 * @param child {qc.Node} 待查找的孩子对象
 * @return {Number} 孩子的位置
 */
Node.prototype.getChildIndex = function(child) {
    return this.children.indexOf(child);
};

/**
 * 设置孩子的位置
 *
 * @method setChildIndex
 * @param child {qc.Node} 待设置的孩子对象
 * @param index {Number} 放入的位置
 */
Node.prototype.setChildIndex = function(child, index) {
    if (child.parent !== this) {
        return;
    }
    var phaserChildren = this.phaser.children;
    var currentIndex = phaserChildren.indexOf(child.phaser);
    if (currentIndex === index) {
        return;
    }
    phaserChildren.splice(currentIndex, 1); //remove from old position
    phaserChildren.splice(index, 0, child.phaser); //add at new position

    // 派发孩子变化事件
    this._dispatchModelChangeEvent('parent', child);

    this._dispatchChildrenChanged('order', [child]);
};

/**
 * 取得指定位置的孩子对象
 *
 * @method getChildAt
 * @param index {Number} 孩子的位置
 * @return {qc.Node} 如果存在的话，返回孩子对象
 */
Node.prototype.getChildAt = function(index) {
    return this.children[index];
};

/**
 * 移除一个孩子
 *
 * @method removeChild
 * @param child {qc.Node} 待移除的对象或者其位置
 * @return {qc.Node} 被移除后的对象
 */
Node.prototype.removeChild = function(child) {
    // 接触父子关系
    if (this.phaser.remove) {
        this.phaser.remove(child.phaser);
    }
    else {
        this.phaser.removeChild(child.phaser);
    }

    // 无效化
    this._children = null;

    // 派发孩子变化事件
    this._dispatchModelChangeEvent('parent', child);
    return child;
};

/**
 * 在指定的位置删除孩子
 * @param index 要删除的孩子索引
 * @returns {qc.Node} 被删除的孩子对象
 */
Node.prototype.removeChildAt = function(index) {
    var child = this.getChildAt(index);
    return this.removeChild(child);
};

/**
 * 删除所有孩子
 */
Node.prototype.removeChildren = function() {
    // Phaser.Group类型采用removeAll
    if (this.phaser.removeAll) {
        this.phaser.removeAll();
    }
    else {
        this.phaser.removeChildren();
    }
    // 派发孩子删除事件
    this._dispatchModelChangeEvent('removeChildren', this);
};

/**
 * 析构
 */
Node.prototype.onDestroy = function() {
    // 通知父亲移除
    this.parent = null;

    // 通知physer析构
    this.game.nodePool.remove(this.uuid);
};

/**
 * 销毁本对象，该函数不直接进行模型事件派发，
 * 因为Phaser在切换state等情况下会直接调用例如Phaser的destroy函数，
 * 我们无法限制所有destroy调用必须通过Node的函数入口，
 * 所以在构造函数中我们已经重载了Phaser对象的destroy，在那里进行事件派发等处理。
 * 调用本函数时，节点先隐藏掉并且在下一帧才会真正被清理
 */
Node.prototype.destroy = function() {
    if (this._toDestroy) return;
    this._toDestroy = true;
    this.visible = false;

    // 扔到待析构的队列中
    this.game.world.addToDestroy(this);
};

/**
 * 立刻销毁本节点，不推荐使用
 * @method qc.Node#destroyImmediately
 */
Node.prototype.destroyImmediately = function() {
    // 已经析构了，不要重复调用
    if (this._destroy || this._destroying) return;

    // 移除事件关注
    this._events.forEach(function(eventData) {
        eventData[0].remove(eventData[1], eventData[2]);
    });
    this._events = [];

    this.visible = false;
    this._destroying = true;
    if (this.phaser)
        this.phaser.destroy();
    this._destroying = false;
};

/**
 * 在当前节点下，查找子节点。path可以以"/"进行分隔，表示查找的目录层次
 * 例如：find("UIRoot/ui_login/loginBtn/../")
 *
 * @param path {string} - 查找的node路径
 * @return {qc.Node}
 */
Node.prototype.find = function(path) {
    // 就是我自身
    if (path === '' || path === '.' || path === './') return this;
    var arr = path.split("/");
    var curr = this;
    for (var i = 0; i < arr.length - 1; i++) {
        var p = arr[i];
        if (p === '') continue;
        if (p === '..') {
            // 父亲
            curr = this.parent;
            if (curr === this.game.stage) return null;
            continue;
        }

        // 查找儿子
        curr = curr._findByName(arr[i]);
        if (!curr) return curr;
    }

    var p = arr[arr.length - 1];
    if (p === '' || p === '.') return curr;
    if (p === '..') return curr.parent === this.game.stage ? null : curr.parent;
    return curr._findByName(p);
};

/**
 * 查找指定名字的孩子
 * @private
 */
Node.prototype._findByName = function(name) {
    var children = this.children;
    for (var i in children) {
        if (children[i].name === name) {
            return children[i];
        }
    }
};

/**
 * 派发孩子变化事件
 * @param type 事件派发类型：'name'|parent'|'removeChildren'|'destroy'
 * @param target 发生变化的Node对象
 * @private
 */
Node.prototype._dispatchModelChangeEvent = function(type, target) {
    if (type !== 'name') {
        this._children = null;
    }
    var world = this.game.world;
    world.onModelChange.dispatch({
        type: type,
        target: target
    });
};

/**
 * 调用逻辑脚本的方法
 * @private
 */
Node.prototype._sendMessage = function(method, checkRet, event) {
    if (!this.scripts) return;
    var len = this.scripts.length;
    var arg = Array.prototype.slice.call(arguments, 2);
    for (var i = 0; i < len; i++) {
        var script = this.scripts[i];
        if (script && script.enable) {
            var func = script[method];
            if (!func) continue;

            // 调度之，如果有指明返回值为true就停止掉
            var ret = func.apply(script, arg);
            if (checkRet === true && ret === true) return true;
        }
    }
};

/**
 * 对自身和孩子调用方法
 * @private
 */
Node.prototype._broadcast = function(method) {
    if (!this.scripts) return;
    var i = this.scripts.length;
    var arg = Array.prototype.slice.call(arguments, 1);
    while (i--) {
        var script = this.scripts[i];
        if (!script || !script.enable) continue;

        // 如果当前处于editor模式，并且本脚本没有说明是在editor模式下运行，就不要调度了
        if (this.game.device.editor === true && script.runInEditor !== true) continue;

        // 节点在脚本中析构了，就不继续调度了
        if (this._toDestroy || this._destroy) return;

        // 调度之
        var func = script[method];
        if (!func) continue;
        func.apply(script, arg);
    }

    // 把所有的儿子也调度下
    var children = this._children;
    if (!children) children = this.getChildren();
    var length = children.length;
    for (i = 0; i < length; i++) {
        children[i][method](arg);
    }
};

/**
 * 遍历父亲节点，找出关注子节点变化事件的进行通知
 * @param type
 * @param children
 * @private
 */
Node.prototype._dispatchChildrenChanged = function(type, children) {
    var parent = this;
    var event = {
        source : this,
        type : type,
        children : children
    };
    while (parent) {
        if (parent._onChildrenChanged) {
            parent._onChildrenChanged.dispatch(event);
        }
        parent = parent.parent;
    }
};

/**
 * 通知孩子，节点的可视化信息发生了变化
 * @private
 */
Node.prototype._notifyVisibleChanged = function(isVisible) {
    if (this.onVisibleChange) this.onVisibleChange();

    var children = this._children;
    if (!children) children = this.children;
    var i = children.length;
    while (i--) {
        if (!children[i].visible) continue;
        children[i]._notifyVisibleChanged(isVisible);
    }
};

/**
 * 使用该增加事件关注，在 destroy 时会自动移除事件关注，避免事件没被移除，导致对象被引用，内存泄漏
 * @public
 * @chenx 2015.11.11
 */
Node.prototype.addListener = function(signal, listener, listenerContext, priority) {

    // 注册事件
    signal.add(listener, listenerContext, priority);

    // 加入缓存
    this._events.push([signal, listener, listenerContext]);
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * RectTransform部分的实现
 */
Object.defineProperties(Node.prototype, {
    /**
     * @property {number} x
     * 本地X坐标(相对于父亲节点)
     */
    x: {
        get: function () {
            return this.phaser.x;
        },
        set: function (v) {
            if (this.x === v) return;

            // 如果anchor在X方向不重合，需要重新计算下left和right
            if (this.minAnchor.x < this.maxAnchor.x) {
                this._left = this.left + (v - this.x);
                this._right = this.right - (v - this.x);
            }

            // 更新下anchorX的值
            this.phaser.x = v;
            this._calcAnchoredX();

            // 需要更新变换矩阵
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {number} y
     * 本地Y坐标(相对于父亲节点)
     */
    y: {
        get: function() {
            return this.phaser.y;
        },
        set: function(v) {
            if (this.y === v) return;

            // 如果anchor在Y方向不重合，直接设置Y坐标是无效的
            if (this.minAnchor.y < this.maxAnchor.y) {
                this._top = this.top + (v - this.y);
                this._bottom = this.bottom - (v - this.y);
            }

            // 更新下anchorY的值
            this.phaser.y = v;
            this._calcAnchoredY();

            // 需要更新变换矩阵
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {number} 相对于父亲anchor的X偏移量
     */
    anchoredX : {
        get : function() {
            if (this._anchoredX === undefined) {
                // 尚未设置过，根据X坐标来计算下
                this._calcAnchoredX();
            }
            return this._anchoredX;
        },
        set : function(v) {
            if (v === this.anchoredX) return;
            this._anchoredX = v;
            this._calcX();

            // 需要更新变换矩阵
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {number} 相对于父亲anchor的Y偏移量
     */
    anchoredY : {
        get : function() {
            if (this._anchoredY === undefined) {
                // 尚未设置过，根据Y坐标来计算下
                this._calcAnchoredY();
            }
            return this._anchoredY;
        },
        set : function(v) {
            if (v === this.anchoredY) return;
            this._anchoredY = v;
            this._calcY();

            // 需要更新变换矩阵
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {number} scaleX - 节点的在X轴上的缩放
     */
    'scaleX': {
        get: function () {
            return this.phaser.scale.x;
        },
        set: function (v) {
            if (v === this.scaleX)
                return;

            this.phaser.scale.x = v;
            // 需要更新变换矩阵
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {number} scaleY - 节点的在Y轴上的缩放
     */
    'scaleY': {
        get: function () {
            return this.phaser.scale.y;
        },
        set: function (v) {
            if (v === this.scaleY)
                return;

            this.phaser.scale.y = v;
            // 需要更新变换矩阵
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {number} pivotX - 节点自身的原点X位置
     * 0为左边，1为右边
     */
    pivotX : {
        get : function() {
            if (!this.phaser.anchor)
                this.phaser.anchor = new qc.Point(0, 0);
            return this.phaser.anchor.x;
        },
        set : function(v) {
            if (!this.phaser.anchor)
                this.phaser.anchor = new qc.Point(0, 0);
            if (this.phaser.anchor.x === v) return;

            this.phaser.anchor.x = v;
            this.relayout();
        }
    },

    /**
     * @property {number} pivotY - 节点自身的原点Y位置
     * 0为左边，1为右边
     */
    pivotY : {
        get : function() {
            if (!this.phaser.anchor)
                this.phaser.anchor = new qc.Point(0, 0);
            return this.phaser.anchor.y;
        },
        set : function(v) {
            if (!this.phaser.anchor)
                this.phaser.anchor = new qc.Point(0, 0);
            if (this.phaser.anchor.y === v) return;

            this.phaser.anchor.y = v;
            this.relayout();
        }
    },

    /**
     * @property {number} rotation - 旋转角度
     */
    'rotation': {
        get: function () {
            return this.phaser.rotation;
        },
        set: function (v) {
            if (this.phaser.rotation === v) {
                return;
            }
            this.phaser.rotation = v;
            // 需要更新变换矩阵
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {number} skewX - x轴拉伸角度，为兼容 DragonBones添加
     */
    skewX : {
        get: function () {
            return this._skewX;
        },
        set: function (v) {
            if (this._skewX === v) {
                return;
            }
            this._skewX = v;
            // 需要更新变换矩阵
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {number} skewY - y轴拉伸角度，为兼容 DragonBones添加
     */
    skewY : {
        get: function () {
            return this._skewY;
        },
        set: function (v) {
            if (this._skewY === v) {
                return;
            }
            this._skewY = v;
            // 需要更新变换矩阵
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {number} width - 节点的宽度
     */
    'width': {
        // 使用我自己的实现就可以了(如果是Node的话)
        // 如果是其他元素（如UIImage等）需要按照实际情况进行重载实现
        get: function () {
            return this._width = this._width || 0;
        },
        set: function (v) {
            if (this.game.math.fuzzyEqual(this._width, v, 0.001)) return;

            // 如果anchor在X方向不重合，需要重新计算下left和right
            if (this.minAnchor.x < this.maxAnchor.x) {
                var delta = v - this.width;
                var deltaLeft = delta * this.pivotX;
                var deltaRight = delta - deltaLeft;
                this._left -= deltaLeft;
                this._right -= deltaRight;
            }
            else
                this.setWidth(v);
            this.relayout();
        }
    },

    /**
     * @property {number} height - 节点的高度
     */
    'height': {
        // 使用我自己的实现就可以了(如果是Node的话)
        // 如果是其他元素（如UIImage等）需要按照实际情况进行重载实现
        get: function () {
            return this._height = this._height ? this._height : 0;
        },
        set: function (v) {
            if (this.game.math.fuzzyEqual(this._height, v, 0.001)) return;

            // 如果anchor在Y方向不重合，直接设置高度是无效的
            if (this.minAnchor.y < this.maxAnchor.y) {
                var delta = v - this.height;
                var deltaTop = delta * this.pivotY;
                var deltaBottom = delta - deltaTop;
                this._top -= deltaTop;
                this._bottom -= deltaBottom;
            }
            else
                this.setHeight(v);
            this.relayout();
        }
    },

    /**
     * @property {qc.Point} minAnchor
     * @readonly
     * 定位矩形左上角的坐标（相对于父亲长宽的比例值）
     *  (0, 0) - 左上角
     *  (1, 1) - 右下角
     * 注：大小不能超过maxAnchor
     */
    minAnchor : {
        get : function() {
            if (!this._minAnchor)
                this._minAnchor = new qc.Point(0, 0);
            return this._minAnchor;
        },
        set : function(v) {
            throw new Error('user function:setAnchor');
        }
    },

    /**
     * @property {qc.Point} maxAnchor
     * @readonly
     * 定位矩形右下角的坐标（相对于父亲长宽的比例值）
     *  (0, 0) - 左上角
     *  (1, 1) - 右下角
     * 注：大小不能小于minAnchor
     */
    maxAnchor : {
        get : function() {
            if (!this._maxAnchor)
                this._maxAnchor = new qc.Point(0, 0);
            return this._maxAnchor;
        },
        set : function(v) {
            throw new Error('use function:setAnchor');
        }
    },

    /**
     * @property {number} left
     * 离左边的距离，一般在编辑器里面设置
     * 注意：如果minAnchor.x = maxAnchor.x，此时不起效
     */
    left : {
        get : function() {
            if (this._left === undefined)
                this._left = 0;
            return this._left;
        },
        set : function(v) {
            if (this._left === v) return;
            this._left = v;
            this.relayout();
        }
    },

    /**
     * @property {number} right
     * 离右边的距离，一般在编辑器里面设置
     * 注意：如果minAnchor.x = maxAnchor.x，此时不起效
     */
    right : {
        get : function() {
            if (this._right === undefined)
                this._right = 0;
            return this._right;
        },
        set : function(v) {
            if (this._right === v) return;
            this._right = v;
            this.relayout();
        }
    },

    /**
     * @property {number} top
     * 离上边的距离，一般在编辑器里面设置
     * 注意：如果minAnchor.y = maxAnchor.y，此时不起效
     */
    top : {
        get : function() {
            if (this._top === undefined)
                this._top = 0;
            return this._top;
        },
        set : function(v) {
            if (this._top === v) return;
            this._top = v;
            this.relayout();
        }
    },

    /**
     * @property {number} bottom
     * 离下边的距离，一般在编辑器里面设置
     * 注意：如果minAnchor.y = maxAnchor.y，此时不起效
     */
    bottom : {
        get : function() {
            if (this._bottom === undefined)
                this._bottom = 0;
            return this._bottom;
        },
        set : function(v) {
            if (this._bottom === v) return;
            this._bottom = v;
            this.relayout();
        }
    },

    /**
     * @property {qc.Rectangle} rect - 矩形框(相对于父亲节点)
     * @protected
     * @readonly
     */
    rect : {
        get : function() {
            var w = this.width;
            var h = this.height;
            return new qc.Rectangle(-this.width * this.pivotX, -this.height * this.pivotY, w, h);
        }
    },

    /**
     * @property {qc.Matrix} worldTransform - 自身在世界的变形矩阵
     * @protected
     * @readonly
     */
    worldTransform : {
        get : function() {
            this.nodeUpdateTransform();
            return this.phaser.worldTransform;
        }
    },

    /**
     * @property {qc.Rectangle} localBounds - 节点自身的显示范围
     * @readonly
     */
    localBounds : {
        get : function() {
            return this.phaser.getLocalBounds();
        }
    },

    /**
     * @property {boolean} _isTransformDirty - 节点的变化矩阵是否需要重新计算
     */
    _isTransformDirty : {
        get : function() { return !this.phaser._isNotNeedCalcTransform; },
        set : function(v) {
            if (this.phaser._isNotNeedCalcTransform === !v) {
                return;
            }
            this.phaser._isNotNeedCalcTransform = !v;
        }
    },

    /**
     * @property {Phaser.Signal} onLayoutArgumentChanged - 节点布局属性变化，宽高、layoutElement 参数变化
     */
    onLayoutArgumentChanged : {
        get : function() {
            return this._onLayoutArgumentChanged || (this._onLayoutArgumentChanged = new Phaser.Signal());
        }
    },

    /**
     * @property {Phaser.Signal} onRelayout - 节点重布局结束
     */
    onRelayout : {
        get : function() {
            return this._onRelayout || (this._onRelayout = new Phaser.Signal());
        }
    }
});

/**
 * 修改可伸缩参数
 * @param left {number} - 离左边的距离
 * @param right {number} - 离右边的距离
 * @param top {number} - 离上边的距离
 * @param bottom {number} - 离下边的距离
 */
Node.prototype.setStretch = function(left, right, top, bottom) {
    var changed = false;
    if (this.minAnchor.x >= this.maxAnchor.x) {
        if (this._anchoredX !== left) {
            this._anchoredX = left;
            changed = true;
        }
    }
    else {
        if (left !== undefined && this._left !== left) {
            this._left = left;
            changed = true;
        }
        if (right !== undefined && this._right !== right) {
            this._right = right;
            changed = true;
        }
    }

    if (this.minAnchor.y >= this.maxAnchor.y) {
        if (this._anchoredY !== top) {
            this._anchoredY = top;
            changed = true;
        }
    }
    else {
        if (top !== undefined && this._top !== top) {
            this._top = top;
            changed = true;
        }
        if (bottom !== undefined && this._bottom !== bottom) {
            this._bottom = bottom;
            changed = true;
        }
    }

    if (changed) {
        this.relayout();
    }
};

/**
 * 设置定位范围
 *
 * @param {qc.Point} min - 左上角的基准点，如果为undefined，则认为没有变化
 * @param {qc.Point} max - 右下角的基准点，如果为undefined，则认为没有变化
 * @param {boolean} keepRect - 是否保持rect不变，默认为true
 */
Node.prototype.setAnchor = function(min, max, keepRect) {
    keepRect = keepRect === undefined ? true : keepRect;
    min = min || this.minAnchor;
    max = max || this.maxAnchor;
    if (min.x < 0) min.x = 0;
    if (min.y < 0) min.y = 0;
    if (min.x > 1) min.x = 1;
    if (min.y > 1) min.y = 1;
    if (max.x < 0) max.x = 0;
    if (max.y < 0) max.y = 0;
    if (max.x > 1) max.x = 1;
    if (max.y > 1) max.y = 1;
    if (min.x > max.x || min.y > max.y)
        throw new Error('Expected:min < max');

    if (min.x === this.minAnchor.x && min.y === this.minAnchor.y &&
        max.x === this.maxAnchor.x && max.y === this.maxAnchor.y)
        // 没有啥变化，无视
        return;
    this._minAnchor = min;
    this._maxAnchor = max;
    if (keepRect) {
        this._calcAnchoredX();
        this._calcAnchoredY();
        this._calcMargin();
    }
    else {
        this.relayout();
    }
};

/**
 * 重排布下位置
 *
 * @method qc.Node.relayout
 */
Node.prototype.relayout = function() {
    // 先计算父亲的矩形范围
    var rect = this.parent.rect;

    // 处理x方向
    if (this.minAnchor.x < this.maxAnchor.x) {
        // 根据left和right计算其大小以及x的坐标
        var l = rect.x + rect.width * this.minAnchor.x + this.left;
        var r = rect.x + rect.width * this.maxAnchor.x - this.right;

        // 设置其宽度
        // 注意：这里应该非常单纯的设置phaser元素的大小，由于this.width可能会被重载，
        //   同时this.width是不运行在当前模式下设置值的（无效），因此调用了额外封装的setWidth接口
        var w = r - l;
        this.setWidth(w);

        // 图片的中心应该刚好在（l, r)的中心点
        this.phaser.x = l + w * this.pivotX;
        this._calcAnchoredX();
    }
    else {
        // 根据anchorX的值计算其坐标
        this.phaser.x = rect.x + rect.width * this.minAnchor.x + this.anchoredX;
    }

    // 处理y方向
    if (this.minAnchor.y < this.maxAnchor.y) {
        // 根据top和bottom计算其大小以及y的坐标
        var t = rect.y + rect.height * this.minAnchor.y + this.top;
        var b = rect.y + rect.height * this.maxAnchor.y - this.bottom;

        // 设置其高度
        // 注意：这里应该非常单纯的设置phaser元素的大小，由于this.height可能会被重载，
        //   同时this.height是不运行在当前模式下设置值的（无效），因此调用了额外封装的setHeight接口
        var h = b - t;
        this.setHeight(h);

        // 图片的中心应该刚好在（t, b)的中心点
        this.phaser.y = t + h * this.pivotY;
        this._calcAnchoredY();
    }
    else {
        // 根据anchorY的值计算其坐标
        this.phaser.y = rect.y + rect.height * this.minAnchor.y + this.anchoredY;
    }

    // 需要更新变换矩阵
    this._isTransformDirty = true;

    // 所有的孩子也要进行重排
    var children = this.children;
    for (var i = 0; i < children.length; i++) {
        children[i].relayout();
    }
    // 强制计算下新的四个角信息
    this._calcWorldCorners(true);
    this._dispatchLayoutArgumentChanged('size');

    // 重排完毕，需要派发事件
    if (this._onRelayout)
        this._onRelayout.dispatch();
};

/**
 * 设置节点的宽度，可能会被重载，这接口不会引起重排
 * @protected
 */
Node.prototype.setWidth = function(w) {
    this._width = w;
};

/**
 * 设置节点的高度，可能会被重载，这接口不会引起重排
 * @protected
 */
Node.prototype.setHeight = function(h) {
    this._height = h;
};

/**
 * 设置Node节点默认的大小和位置等信息
 * @private
 */
Node.prototype._defaultTransform = function() {
    // 对于world节点，忽略掉
    if (!this.parent || this.parent === this.game.stage) return;

    // 默认大小为100
    this.x = 0;
    this.y = 0;
    this.width = 100;
    this.height = 100;
};

/**
 * 重新计算四周边距
 * @private
 */
Node.prototype._calcMargin = function() {
    var rect = this.parent.rect;

    if (this.minAnchor.x >= this.maxAnchor.x) {
        this._left = this._right = 0;
    }
    else {
        var nodeX = this.x - this.pivotX * this.width;
        var minX = rect.x + rect.width * this.minAnchor.x;
        this._left = nodeX - minX;

        var maxX = rect.x + rect.width * this.maxAnchor.x;
        this._right = maxX - (nodeX + this.width);
    }

    if (this.minAnchor.y >= this.maxAnchor.y) {
        this._top = this._bottom = 0;
    }
    else {
        var nodeY = this.y - this.pivotY * this.height;
        var minY = rect.y + rect.height * this.minAnchor.y;
        this._top = nodeY - minY;

        var maxY = rect.y + rect.height * this.maxAnchor.y;
        this._bottom = maxY - (nodeY + this.height);
    }
};

/**
 * 计算anchorX
 * @private
 */
Node.prototype._calcAnchoredX = function() {
    var rect = this.parent.rect;
    this._anchoredX = this.x - (rect.width * this.minAnchor.x + rect.x);
};

/**
 * 计算anchorY
 * @private
 */
Node.prototype._calcAnchoredY = function() {
    var rect = this.parent.rect;
    this._anchoredY = this.y - (rect.height * this.minAnchor.y + rect.y);
};

/**
 * 计算X
 * @private
 */
Node.prototype._calcX = function() {
    var rect = this.parent.rect;
    this.x = this._anchoredX + (rect.width * this.minAnchor.x + rect.x);
};

/**
 * 计算Y
 * @private
 */
Node.prototype._calcY = function() {
    var rect = this.parent.rect;
    this.y = this._anchoredY + (rect.height * this.minAnchor.y + rect.y);
};

/**
 * 全局坐标转换成本地坐标
 * @param globalPoint 要进行转换的全局坐标
 * @returns {Point} 转换后的本地坐标
 */
Node.prototype.toLocal = function(globalPoint) {
    return this.worldTransform.applyInverse(globalPoint);
};

/**
 * 本地坐标转换成全局坐标
 * @param localPoint 要进行转换的本地坐标
 * @returns {Point} 转换后的全局坐标
 */
Node.prototype.toGlobal = function(localPoint) {
    return this.worldTransform.apply(localPoint);
};

/**
 * 更新自身的变换矩阵
 * @protected
 * @return 矩阵是否变化了
 */
Node.prototype.nodeUpdateTransform = function() {
    var hasChange = false;
    // 如果存在父节点则更新父节点的变换矩阵
    if (this.parent && this.parent.nodeUpdateTransform)
    {
        hasChange = this.parent.nodeUpdateTransform();
    }
    if (!this._isTransformDirty)
        // 自身的变化矩阵无需变化
        return hasChange;
    // 父节点更新 updateTransform后，会将所有的子节点都进行刷新
    if (!hasChange) {
        this.phaser.updateTransform();
    }
    //this._isTransformDirty = false;
    return true;
};

/**
 * 获取节点在全局坐标系中的位置
 * @returns {Point} 在全局坐标系中的位置
 */
Node.prototype.getWorldPosition = function() {
    var matrix = this.worldTransform;
    return new qc.Point(matrix.tx, matrix.ty);
};

/**
 * 获取节点在全局坐标系中的缩放
 * @returns {point} 在全局坐标系中的缩放
 */
Node.prototype.getWorldScale = function() {
    var matrix = this.worldTransform;
    var skewY = Math.atan2(matrix.b, matrix.a);
    var skewX = Math.atan2(-matrix.c, matrix.d);
    // 当斜率一致，但skew值不同时，需要特俗处理
    if (matrix.b / matrix.a === -matrix.c / matrix.d && skewX !== skewY) {
        skewY = Math.atan(matrix.b / matrix.a);
        skewX = Math.atan(-matrix.c / matrix.d);
    }
    var scaleX = skewY === Math.PI || skewY === 0  || (matrix.b === 0 && matrix.a !== 0) ? matrix.a / Math.cos(skewY) : matrix.b / Math.sin(skewY);
    var scaleY = skewX === Math.PI || skewX === 0  || (matrix.c === 0 && matrix.d !== 0) ? matrix.d / Math.cos(skewX) : -matrix.c / Math.sin(skewX);
    return new qc.Point(scaleX, scaleY);
};

/**
 * 获取节点在全局坐标系中的斜拉偏移，如果x,y轴的斜拉角度一致，退化为Rotation参数，返回{x=0, y=0}
 * 注意：Rotation和Skew只能有一个存在有效值
 * @returns {point} 在全局坐标系中的斜拉偏移
 */
Node.prototype.getWorldSkew = function() {
    var matrix = this.worldTransform;

    if (matrix.b / matrix.a === -matrix.c / matrix.d)
        // x,y轴斜拉的角度一致
        return new qc.Point(0, 0);

    return new qc.Point(Math.atan2(-matrix.c, matrix.d),
                        Math.atan2(matrix.b, matrix.a));
};

/**
 * 获取节点在全局坐标系中的旋转wor
 * 注意：Rotation和Skew只能有一个存在有效值
 * @returns {point} 在全局坐标系中的旋转
 */
Node.prototype.getWorldRotation = function() {
    var matrix = this.worldTransform;

    if (matrix.b / matrix.a !== -matrix.c / matrix.d)
        // 现在是斜拉变形
        return 0;

    return Math.atan2(matrix.b, matrix.a);
};

/**
 * 设置当前节点相对于World的变换信息
 * @parem target {Node} 需要设置的坐标系节点
 * @param x {number} 在世界坐标系中距离原点在x轴上的偏移位置
 * @param y {number} 在世界坐标系中距离原点在y轴上的偏移位置
 * @param scaleX {number} 在世界坐标系中距离原点在x轴上的缩放比例
 * @param scaleY {number} 在世界坐标系中距离原点在y轴上的缩放比例
 * @param rotation {number} 在世界坐标系中旋转的弧度值
 */
Node.prototype.setTransformToWorld = function(x, y, scaleX, scaleY, rotation) {
    this.setTransformTo(this.game.world, x, y, scaleX, scaleY, rotation);
};

/**
 * 设置当前节点相对于target的变换信息
 * @parem target {Node} 需要设置的坐标系节点
 * @param x {number} 在世界坐标系中距离原点在x轴上的偏移位置
 * @param y {number} 在世界坐标系中距离原点在y轴上的偏移位置
 * @param scaleX {number} 在世界坐标系中距离原点在x轴上的缩放比例
 * @param scaleY {number} 在世界坐标系中距离原点在y轴上的缩放比例
 * @param rotation {number} 在世界坐标系中旋转的弧度值
 */
Node.prototype.setTransformTo = function(target, x, y, scaleX, scaleY, rotation) {
    x = x || 0;
    y = y || 0;
    scaleX = scaleX || 1;
    scaleY = scaleY || 1;
    rotation = rotation || 0;

    var skewX = 0;
    var skewY = 0;
    if (arguments.length > 6 && typeof arguments[6] === 'number')
        skewX = arguments[6];
    if (arguments.length > 7 && typeof arguments[7] === 'number')
        skewY = arguments[7];

    var cosSkewX = Math.cos(skewX);
    var sinSkewX = Math.sin(skewX);
    var cosSkewY = Math.cos(skewY);
    var sinSkewY = Math.sin(skewY);

    var cosRot = Math.cos(rotation);
    var sinRot = Math.sin(rotation);

    // 形变的最终变换矩阵结果
    var a = (cosRot * cosSkewY - sinRot * sinSkewX) * scaleX;
    var b = (cosRot * sinSkewY + sinRot * cosSkewX) * scaleX;
    var c = (-sinRot * cosSkewY - cosRot * sinSkewX) * scaleY;
    var d = (-sinRot * sinSkewY + cosRot * cosSkewX) * scaleY;
    var tx = x;
    var ty = y;

    this.setTransformToWithMatrix(target, a, b, c, d, tx, ty);
};

/**
 * 设置当前节点相对于target的变换矩阵信息
 * @param target {Node} 需要设置的坐标系节点
 * @param a {number}
 * @param b {number}
 * @param c {number}
 * @param d {number}
 * @param tx {number}
 * @param ty {number}
 */
Node.prototype.setTransformToWithMatrix = function(target, a, b, c, d, tx, ty) {
    var worldMatrix = [a, b, 0, c, d, 0, tx, ty, 1];
    if (target)
    {
        var targetTrans = target ? (target.worldTransform || new qc.Matrix()) : new qc.Matrix();
        var targetMatrix = targetTrans.toArray(true);
        this.game.math.multiply(worldMatrix, targetMatrix, worldMatrix);
    }

    // 获得父节点的transform
    var parentTrans = this.parent ? (this.parent.worldTransform || new qc.Matrix()) : new qc.Matrix();
    var parentMatrix = parentTrans.toArray(true);
    var out = [];

    // 计算出相对父节点的偏移
    this.game.math.invert(out, parentMatrix);
    this.game.math.multiply(out, out, worldMatrix);

    var a = out[0];
    var b = out[1];
    var c = out[3];
    var d = out[4];

    this.x = out[6];
    this.y = out[7];
    if (b / a !== -c / d)
    {
        // 取斜拉变形
        var skewY = Math.atan2(b, a);
        var skewX = Math.atan2(-c, d);
        this.rotation = skewY;
        //this.rotation = 0;

        this.scaleX = skewY === 0 || (b === 0 && a !== 0)  ? a / Math.cos(skewY) : b / Math.sin(skewY);
        this.scaleY = skewX === 0  ||(c === 0 && d !== 0) ? d / Math.cos(skewX) : -c / Math.sin(skewX);
        //throw new Error('Node不支持Skew形变, skewX = ' + skewX + ',skewY = ' + skewY);
    }
    else {
        // 取旋转变形
        //this.skewX = this.skewY = 0;
        var rotation = Math.atan2(b, a);
        this.rotation = rotation;
        this.scaleX = rotation === 0 || (b === 0 && a !== 0) ? a / Math.cos(rotation) : b / Math.sin(rotation);
        this.scaleY = rotation === 0 || (c === 0 && d !== 0) ? d / Math.cos(rotation) : -c / Math.sin(rotation);
    }

    // 重新计算anchoredX、anchoredY和margin参数
    this._calcAnchoredX();
    this._calcAnchoredY();
    this._calcMargin();
};

/**
 * 为了不将RectTransform的代码逻辑污染到该文件以外，重载Node的addChildAt函数，
 * 后续处理调用孩子relayout布局，以及设置_isTransformDirty脏标志
 */
var oldAddChildAt = Node.prototype.addChildAt;
Node.prototype.addChildAt = function(child, index) {
    // 调用原始实现
    oldAddChildAt.call(this, child, index);
    // 如果不是switchParent函数触发，则需要进行relayout
    if (!child._isSwitchParent) {
        child.relayout();
    }
    child._isTransformDirty = true;
};

/**
 * 切换父节点为指定节点，并保证本节点在世界坐标系中的状态不变
 * @param parent 要切换到的父亲节点
 * @param index 所在父亲节点的孩子位置索引，为空代表插入到最后
 */
Node.prototype.switchParent = function(parent, index) {
    var worldTransBak = this.worldTransform;
    var a = worldTransBak.a;
    var b = worldTransBak.b;
    var c = worldTransBak.c;
    var d = worldTransBak.d;
    var tx = worldTransBak.tx;
    var ty = worldTransBak.ty;

    // 设置标志避免在设置父子关系的addChildAt函数中进行relayout，
    // 我们已经通过矩阵运算保持节点状态不变了，如果进行relayout会导致状态再次变化
    this._isSwitchParent = true;
    this.parent = parent;
    delete this._isSwitchParent;

    // 设置会原来index位置
    if (index != null) {
        this.parent.setChildIndex(this, index);
    }

    this.setTransformToWithMatrix(null, a, b, c, d, tx, ty);
};

/**
 * 计算世界坐标下的顶点信息
 * @param {boolean} forceUpdate - 是否强制更新
 * @private
 */
Node.prototype._calcWorldCorners = function(forceUpdate) {
    // 只有强制刷新和矩阵变化时才重新计算点信息
    if (forceUpdate || this.nodeUpdateTransform()) {
        var rect = this.rect;
        var leftTop = new qc.Point(rect.x, rect.y);
        var rightTop = new qc.Point(rect.x + rect.width, rect.y);
        var rightBottom = new qc.Point(rect.x + rect.width, rect.y + rect.height);
        var leftBottom = new qc.Point(rect.x, rect.y + rect.height);

        var worldTransform = this.phaser.worldTransform;

        this._worldLeftTop = worldTransform.apply(leftTop);
        this._worldRightTop = worldTransform.apply(rightTop);
        this._worldRightBottom = worldTransform.apply(rightBottom);
        this._worldLeftBottom = worldTransform.apply(leftBottom);

        var worldMinX = Math.min(this._worldLeftTop.x, this._worldRightTop.x, this._worldRightBottom.x, this._worldLeftBottom.x);
        var worldMaxX = Math.max(this._worldLeftTop.x, this._worldRightTop.x, this._worldRightBottom.x, this._worldLeftBottom.x);
        var worldMinY = Math.min(this._worldLeftTop.y, this._worldRightTop.y, this._worldRightBottom.y, this._worldLeftBottom.y);
        var worldMaxY = Math.max(this._worldLeftTop.y, this._worldRightTop.y, this._worldRightBottom.y, this._worldLeftBottom.y);
        this._worldBox = new qc.Rectangle(worldMinX, worldMinY, worldMaxX - worldMinX, worldMaxY - worldMinY);
    }
};

/**
 * 获取节点在世界坐标中的四个角
 * @return {[qc.Point]}
 */
Node.prototype.getWorldCorners = function() {
    this._calcWorldCorners();
    return [this._worldLeftTop, this._worldRightTop, this._worldRightBottom, this._worldLeftBottom];
};

/**
 * 获取节点在指定节点坐标中的四个角
 * @param {qc.Node} target - 目标节点
 * @return {[qc.Point]}
 */
Node.prototype.getCornersIn = function(target) {
    var targetTransform = target && target.phaser.worldTransform;
    this._calcWorldCorners();
    return !targetTransform ?
            [this._worldLeftTop, this._worldRightTop, this._worldRightBottom, this._worldLeftBottom] :
            [targetTransform.applyInverse(this._worldLeftTop),
            targetTransform.applyInverse(this._worldRightTop),
            targetTransform.applyInverse(this._worldRightBottom),
            targetTransform.applyInverse(this._worldLeftBottom)];
};

/**
 * 获取节点在世界坐标的包围盒
 * @return {qc.Rectangle}
 */
Node.prototype.getWorldBox = function() {
    this._calcWorldCorners();
    return this._worldBox;
};

/**
 * 获取节点在指定节点坐标系中的包围盒
 * @param {qc.Node} target - 目标节点
 * @return {qc.Rectangle}
 */
Node.prototype.getBoxIn = function(target) {
    var corners = this.getCornersIn(target);
    var maxPos = new qc.Point(-Number.MAX_VALUE, -Number.MAX_VALUE);
    var minPos = new qc.Point(Number.MAX_VALUE, Number.MAX_VALUE);
    for (var i = 0; i < 4; i++) {
        var one = corners[i];
        maxPos.x = Math.max(maxPos.x, one.x);
        maxPos.y = Math.max(maxPos.y, one.y);
        minPos.x = Math.min(minPos.x, one.x);
        minPos.y = Math.min(minPos.y, one.y);
    }
    return new qc.Rectangle(minPos.x, minPos.y, maxPos.x - minPos.x, maxPos.y - minPos.y);
};

/**
 * 判断世界坐标中的某个点是否在矩形框的范围内
 * @param globalPoint
 * @returns {boolean}
 */
Node.prototype.rectContains = function(globalPoint) {
    var point = this.toLocal(globalPoint);
    return this.rect.contains(point.x, point.y);
};

/**
 * 通知影响布局参数的改变
 * @private
 */
Node.prototype._dispatchLayoutArgumentChanged = function(type) {
    if (this._onLayoutArgumentChanged) {
        this._onLayoutArgumentChanged.dispatch(type);
    }
};
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * Input交互部分的实现
 */
Object.defineProperties(Node.prototype, {
    /**
     * This is the defined area that will pick up mouse / touch events. It is null by default.
     * Setting it is a neat way of optimising the hitTest function that the interactionManager will use (as it will not need to hit test all the children)
     *
     * @property hitArea
     * @type Rectangle|Circle|Ellipse|Polygon
     */
    'hitArea': {
        get: function() {
            return this.phaser.hitArea === undefined ? this.rect : this.phaser.hitArea;
        },
        set: function(v) {
            this.phaser.hitArea = v;
        }
    },

    /**
     * 返回原生态的 hitArea，用于序列化反序列化
     * @property hitArea
     * @type Rectangle|Circle|Ellipse|Polygon
     */
    'serializeHitArea': {
        get: function() {
            return this.phaser.hitArea;
        },
        set: function(v) {
            this.phaser.hitArea = v;
        }
    },

    /**
     * 获取点下事件派发器
     */
    'onDown': {
        get: function() {
            if (!this._onDown) {
                this._onDown = new Phaser.Signal();
            }
            return this._onDown;
        }
    },

    /**
     * 获取抬起事件派发器
     */
    'onUp': {
        get: function() {
            if (!this._onUp) {
                this._onUp = new Phaser.Signal();
            }
            return this._onUp;
        }
    },

    /**
     * 获取点击事件派发器
     */
    'onClick': {
        get: function() {
            if (!this._onClick) {
                this._onClick = new Phaser.Signal();
            }
            return this._onClick;
        }
    },

    /**
     * 获取拖拽开始事件派发器
     */
    'onDragStart': {
        get: function() {
            if (!this._onDragStart) {
                this._onDragStart = new Phaser.Signal();
            }
            return this._onDragStart;
        }
    },

    /**
     * 获取拖拽过程事件派发器
     */
    'onDrag': {
        get: function() {
            if (!this._onDrag) {
                this._onDrag = new Phaser.Signal();
            }
            return this._onDrag;
        }
    },

    /**
     * 获取拖拽结束事件派发器
     */
    'onDragEnd': {
        get: function() {
            if (!this._onDragEnd) {
                this._onDragEnd = new Phaser.Signal();
            }
            return this._onDragEnd;
        }
    },

    /**
     * 拖拽放下事件
     */
    'onDragDrop' : {
        get: function() {
            if (!this._onDragDrop) {
                this._onDragDrop = new Phaser.Signal();
            }
            return this._onDragDrop;
        }
    },

    /**
     * 滚轮滚动事件派发器
     */
    'onWheel': {
        get: function() {
            if (!this._onWheel) {
                this._onWheel = new Phaser.Signal();
            }
            return this._onWheel;
        }
    },

    /**
     * 光标进入事件
     */
    'onEnter': {
        get: function() {
            if (!this._onEnter) {
                this._onEnter = new Phaser.Signal();
            }
            return this._onEnter;
        }
    },

    /**
     * 光标移出事件
     */
    'onExit': {
        get: function() {
            if (!this._onExit) {
                this._onExit = new Phaser.Signal();
            }
            return this._onExit;
        }
    },

    /**
     * 是否可交互状态变化事件
     */
    'onInteractive': {
        get: function() {
            if (!this._onInteractive) {
                this._onInteractive = new Phaser.Signal();
            }
            return this._onInteractive;
        }
    },

    /**
     * 是否允许交互，默认为false，控制onDown/onUp/onClick等事件
     *
     * @property {boolean} interactive
     */
    'interactive': {
        get: function() {
            return this._interactive;
        },
        set: function(v) {
            this._interactive = v;

            if (this._onInteractive) {
                this._onInteractive.dispatch();
            }
        }
    }
});

/**
 * @method 是否允许拖拽放下，如果需要检测拖拽放下，需要在脚本或者 Node 中实现isAllowDrop
 * @param draggingNode
 */
Node.prototype.checkAllowDrop = function(draggingNode) {
    var scriptDeny = false;
    // 判断所有脚本中是否有有处理该函数
    var len = this.scripts.length;
    for (var i = 0; i < len; i++) {
        var script = this.scripts[i];
        if (script) {
            var func = script['isAllowDrop'];
            if (!func) continue;
            if (func.call(script, draggingNode))
                return true;
            scriptDeny = true;
        }
    }
    // 判断自己是否有实现该接口, 如果自己没有实现且被脚本拒绝则拒绝
    return !this.isAllowDrop ? !scriptDeny : this.isAllowDrop(draggingNode);
};

/**
 * @method 是否可以处理指定的事件类型
 * @param eventType {string} - 事件类型
 * @return {boolean} - 是表示可以处理指定事件类型，否则无法处理
 */
Node.prototype.canHandleInputEvent = function(eventType) {
    // 不可交互时，直接返回
    if (!this.interactive)
        return false;

    // 先判断事件是否有外部监听
    // 直接获取私有属性，避免触发构造
    var eventSignal = this['_' + eventType];
    if (eventSignal && eventSignal.getNumListeners() > 0)
        return true;

    // 判断脚本中是否有注册处理函数
    var len = this.scripts.length;
    for (var i = 0; i < len; i++) {
        var script = this.scripts[i];
        if (script) {
            var func = script[eventType];
            if (!func) continue;

            return true;
        }
    }
    return false;
};

/**
 * @method 通过节点向上查找可以处理指定事件的节点，如果找不到返回null
 * @param eventType {string | [string]} - 事件类型
 * @returns {qc.Node | null} - 可以处理事件的节点或者null
 */
Node.prototype.getInputEventHandle = function(eventType) {
    var eventList = Array.isArray(eventType) ? eventType : [eventType];
    var node = this;
    // 根节点为stage,当找到stage时退出查找
    var rootNode = this.game.stage;
    var i;
    while (node && node !== rootNode) {
        i = eventList.length;
        while (i--) {
            if (node.canHandleInputEvent(eventList[i]))
                return node;
        }
        node = node.parent;
    }
    return null;
};

/**
 * @method 通过节点向上查找可以处理指定事件的节点，如果找不到返回null
 * @param eventType {string | [string]} - 事件类型
 * @returns {[qc.Node, String] | null} - 可以处理事件的节点及事件或者null
 */
Node.prototype.getInputEventHandleAndEventType = function(eventType) {
    var eventList = Array.isArray(eventType) ? eventType : [eventType];
    var node = this;
    // 根节点为stage,当找到stage时退出查找
    var rootNode = this.game.stage;
    var i;
    while (node && node !== rootNode) {
        i = eventList.length;
        while (i--) {
            if (node.canHandleInputEvent(eventList[i]))
                return [node, eventList[i]];
        }
        node = node.parent;
    }
    return null;
};

/**
 * @method 向节点发送交互事件调用
 * @param eventType {string} - 事件类型
 * @param event {object} - 事件参数
 */
Node.prototype.fireInputEvent = function(eventType, event) {
    // 不可交互时，直接返回
    if (!this.interactive || this.state === qc.UIState.DISABLED)
        return false;

    // 通知所有脚本，如果是事件处理完成则中止调用
    var ret = this._sendMessage(eventType, true, event);
    if (ret === true) {
        return true;
    }

    // 先判断事件是否有外部监听
    // 直接获取私有属性，避免触发构造
    var eventSignal = this['_' + eventType];
    if (eventSignal && eventSignal.getNumListeners() > 0) {
        eventSignal.dispatch(this, event);
    }
};
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * @property {string} class - 类名字
 * @readonly
 * @internal
 */
Object.defineProperty(Node.prototype, 'class', {
    get : function() { return 'qc.Node' }
});

/**
 * Node需要序列化的属性描述
 */
Node.prototype.getMeta = function() {
    var s = qc.Serializer;
    var self = this;
    return {
        // 基础信息
        uuid : {
            type : s.STRING,
            get : function(ob, context) {
                return ob.uuid;
            },
            set : function(context, v) {
                // 记录新的uuid与旧的关系
                self.game.serializer.restoreContext[v] = self.uuid;
            }
        },
        _prefab : s.STRING,
        name : s.STRING,
        ignoreDestroy : s.BOOLEAN,
        alpha : s.NUMBER,
        visible : s.BOOLEAN,
        colorTint : s.COLOR,
        static : s.BOOLEAN,
        scripts : {
            get : function(ob, context) {
                return self._packScripts.call(ob, context);
            },
            set : function(context, v) {
                self._unpackScripts.call(context, v);
            }
        },

        // 位置相关部分的序列化
        position : {
            get : function(ob, context) {
                return [ob.pivotX, ob.pivotY, ob.anchoredX, ob.anchoredY, // 0
                    ob.x, ob.y, ob.width, ob.height,                      // 4
                    ob.left, ob.right, ob.top, ob.bottom,                 // 8
                    ob.minAnchor.x, ob.minAnchor.y, ob.maxAnchor.x, ob.maxAnchor.y // 12
                ];
            },
            set : function(context, v) {
                // 注意反序列化的顺序
                var ob = context;
                ob._minAnchor = new qc.Point(v[12], v[13]);
                ob._maxAnchor = new qc.Point(v[14], v[15]);
                ob.phaser.anchor = new qc.Point(v[0], v[1]);
                ob._anchoredX = v[2];
                ob._anchoredY = v[3];

                // 根据情况确定使用边距还是长宽
                if (ob._minAnchor.x < ob._maxAnchor.x) {
                    ob._left = v[8];
                    ob._right = v[9];
                }
                else {
                    ob.setWidth(v[6]);
                }
                if (ob._minAnchor.y < ob._maxAnchor.y) {
                    ob._top = v[10];
                    ob._bottom = v[11];
                }
                else {
                    ob.setHeight(v[7]);
                }
                ob.relayout();
            }
        },
        scaleX : s.NUMBER,
        scaleY : s.NUMBER,
        rotation : s.NUMBER,

        // 交互信息
        serializeHitArea : s.AUTO,

        interactive : s.BOOLEAN,

        isFiltersThrough : s.BOOLEAN,

        // 孩子应该在最后面再来序列化和反序列化
        children : {
            get : function(ob, context) {
                return self._packChildren.call(ob, context);
            },
            set : function(context, v) {
                self._unpackChildren.call(context, v);
            }
        }
    };
};

/**
 * 序列化我的孩子
 * @private
 */
Node.prototype._packChildren = function(context) {
    var children = this.children;
    if (children.length === 0)
        return [];

    // 如果是Text/Sprite等，就不要序列化孩子了（不能挂载其他节点）
    if (this instanceof qc.UIText || this instanceof qc.Sprite)
        return [];

    var list = [];
    for (var i in children) {
        var child = children[i];
        var json = this.game.serializer.buildBundle(child, context);
        list.push(json);
    }
    return list;
};

/**
 * 反序列化我的孩子列表
 * @private
 */
Node.prototype._unpackChildren = function(data) {
    for (var i in data) {
        var child = this.game.serializer.restoreBundle(data[i], this, true);
    }
};

/**
 * 序列化挂载的脚本
 * @private
 */
Node.prototype._packScripts = function(context) {
    var scripts = this.scripts;
    if (!scripts || scripts.length === 0)
        return [];

    var list = [];
    for (var i in scripts) {
        var c = scripts[i],
            data = null;
        if (c._clazz === 'qc.NonexistScript') {
            // 丢失的脚本组件特殊处理下
            list.push({
                clazz: c.script,
                data: c.data.uuid[1]
            });
            continue;
        }
        else {
            if (this.__json && this.__json[c.uuid]) {
                data = this.__json[c.uuid].data;
            }
            data = this.game.serializer.buildBundle(c, context, data);
        }
        
        list.push({
            clazz: c._clazz,
            data: c.uuid
        });
        
        // 记录下序列化结果
        if (!this.__json) {
            this.__json = {
            };
        }
        this.__json[c.uuid] = data;
    }
    return list;
};

/**
 * 反序列化挂载的脚本
 * @private
 */
Node.prototype._unpackScripts = function(data) {
    for (var i in data) {
        var scriptJson = data[i];
        var d = scriptJson.data;
        if (typeof d === 'string') {
            // 兼容旧版本(新版本从__json中获取)
            d = this.__json[d].data;
        }
        else {
            d = d.data;
        }
        
        var clazz = qc.Util.findClass(scriptJson.clazz);
        if (typeof clazz !== 'function') {
            if (d && d.uuid) {
                var c = this.addScript('qc.NonexistScript');
                c.script = scriptJson.clazz;
                c.data = d;
            }
            console.error('Class: ' + scriptJson.clazz + ' is not exist!');
            continue;
        }
        
        try {
            // 还原出脚本的属性信息
            var c = this.addScript(scriptJson.clazz, false);
            var meta = {};
            if (c.getMeta)
                meta = c.getMeta();
            for (var k in meta) {
                try {
                    this.game.serializer.fromJson(c, d, k, meta[k]);    
                }
                catch (e) {
                    console.error('Deserialize fail!', e);
                    qc.Util.popupError(e.message); 
                }
            }  
        }
        catch (e) {
            console.error('Deserialize error!', e);
            qc.Util.popupError(e.message); 
        }
    }
    
    if (!this.game.device.editor) {
        delete this.__json;
    }
};

/**
 * 查找节点ob相对于本节点的路径（序列化时需要获取到），例如：
 * /ob
 * /../../p2/ob
 *
 * @private
 */
Node.prototype._findPath = function(ob, searchParent) {
    var path = '';
    if (ob === this) return path;
    if (searchParent === undefined) searchParent = true;

    // 在我的孩子中寻找
    var children = this.children;
    for (var i in children) {
        var node = children[i];
        var path2 = node._findPath(ob, false);
        if (path2 === '') {
            // 找到了，孩子即为目标节点
            path += node.name;
            return path;
        }
        else if (path2) {
            // 孩子的子孙为目标节点
            path += node.name + '/' + path2;
            return path;
        }
    }

    // 在我的孩子中还是没有找到，需要王父亲节点上查找了
    if (!searchParent) return null;
    var curr = this.parent;
    path += '/..';
    while (curr !== this.game.stage) {
        var path2 = curr._findPath(ob, false);
        if (path2 === '') {
            // 找到了，祖先即为目标节点
            return path;
        }
        else if (path2) {
            // 找到了，祖先的子孙为目标节点
            path += "/" + path2;
            return path;
        }

        // 继续下一级查找
        curr = curr.parent;
        path += '/..';
    }

    // 没有找到
    return null;
};

/**
 * 关联Node的引用关系
 * @private
 */
Node.prototype._restoreNodeRef = function() {
    // 还原我的关联和所有逻辑脚本的关联
    var meta = {};
    if (this.getMeta)
        meta = this.getMeta();
    for (var k in meta) {
        // 处理单个节点
        var k2 = '__BUILTIN_NODE__' + k;
        if (this[k2] && typeof this[k2] === 'string') {
            this[k] = this.game.serializer.findNodeRef(this[k2]);
            delete this[k2];
        }

        // 处理多个节点
        var k3 = '__BUILTIN_NODE_ARRAY__' + k;
        if (this[k3] && typeof this[k3] === 'object') {
            var list = [];
            for (var i in this[k3]) {
                if (this[k3][i] && this[k3][i][1])
                    list.push(this.game.serializer.findNodeRef(this[k3][i][1]));
                else
                    list.push(null);
            }
            this[k] = list;
            delete this[k3];
        }
    }
    for (var i in this.scripts) {
        // 处理单个节点
        var script = this.scripts[i];
        var meta = {};
        if (script.getMeta)
            meta = script.getMeta();
        for (var k in meta) {
            var k2 = '__BUILTIN_NODE__' + k;
            if (script[k2] && typeof script[k2] === 'string') {
                script[k] = this.game.serializer.findNodeRef(script[k2]);
                delete script[k2];
            }

            // 处理多个节点
            var k3 = '__BUILTIN_NODE_ARRAY__' + k;
            if (script[k3] && typeof script[k3] === 'object') {
                var list = [];
                for (var i in script[k3]) {
                    if (script[k3][i] && script[k3][i][1])
                        list.push(this.game.serializer.findNodeRef(script[k3][i][1]));
                    else
                        list.push(null);
                }
                script[k] = list;
                delete script[k3];
            }
        }
    }

    // 还原我的孩子
    var children = this.children;
    for (var k in children) {
        children[k]._restoreNodeRef();
    }
};

/**
 * 字段序列化完成后的处理工作
 * @private
 */
Node.prototype._restoreInit = function() {
    this._initGetSetField();

    // 调用所有脚本的初始化工作
    for (var i in this.scripts) {
        var script = this.scripts[i];
        if (script._restoreInit)
            script._restoreInit();
    }

    // 所有孩子也派发下
    var children = this.children;
    for (var k in children) {
        children[k]._restoreInit();
    }
};

/**
 * 序列化完成后派发awake事件
 * @private
 */
Node.prototype._dispatchAwake = function() {
    // 通知所有的孩子派发本事件
    var children = this.children;
    for (var k in children) {
        children[k]._dispatchAwake();
    }

    // 所有的脚本派发awake事件
    for (var s in this.scripts) {
        var script = this.scripts[s];

        // 编辑器模式下不要派发（如果不声明runInEditor）
        if (this.game.device.editor === true && !script.runInEditor) continue;
        if (script.awake)
            script.awake.call(script);
    }
};

/**
 * 序列化完成后派发enable事件
 * @private
 */
Node.prototype._dispatchEnable = function() {
    // 通知所有的孩子派发本事件
    var children = this.children;
    for (var k in children) {
        children[k]._dispatchEnable();
    }

    // 所有的脚本派发enable事件
    for (var s in this.scripts) {
        var script = this.scripts[s];

        // 编辑器模式下不要派发（如果不声明runInEditor）
        if (this.game.device.editor === true && !script.runInEditor) continue;
        if (script.enable && script.onEnable)
            script.onEnable();
    }
};

/**
 * get/set字段序列化完的初始化工作
 * @private
 */
Node.prototype._initGetSetField = function() {
    var list = this.__GET_SET__;
    if (!list) return;

    for (var i in list) {
        var field = list[i];
        this[field] = this['__BUILTIN_SET__' + field];
        delete this['__BUILTIN_SET__' + field];
    }
    delete this.__GET_SET__;
};

/**
 * get/set字段的反序列化
 * @private
 */
var _CUSTOM_FIELD = function(field, type) {
    return {
        type : type,
        get : function(context) { return context[field]; },
        set : function(context, v) {
            context['__BUILTIN_SET__' + field] = v;

            // 记录下字段
            if (context.__GET_SET__) context.__GET_SET__.push(field);
            else
                context.__GET_SET__ = [ field ];
        }
    };
};
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 拓展 Node 的绘制属性
 */
/**
 * 生成一张缓存图片
 * @param resolution {Number} - 生成的截图的解析度
 * @param scaleMode {Number} - {{#crossLink "qc/scaleModes:property"}}qc.scaleModes{{/crossLink}}
 * @param offX {Number} - 生成截图对于实际绘制区域的 x 轴偏移
 * @param offY {Number} - 生成截图对于实际绘制区域的 y 轴偏移
 * @param width {Number} - 生成截图的宽度
 * @param height {Number} - 生成截图的高度
 * @return {qc.RenderTexture}
 */
Node.prototype.generateTexture = function(resolution, scaleMode, offX, offY, width, height) {
    if (!this.phaser) {
        return new qc.RenderTexture(0, 0, renderer, scaleMode, resolution);
    }
    var bounds = this.localBounds;
    bounds.x += offX || 0;
    bounds.y += offY || 0;
    isNaN(width) || (bounds.width = width);
    isNaN(height) || (bounds.height = height);

    var renderTexture = new qc.RenderTexture(bounds.width | 0, bounds.height | 0, null, scaleMode, resolution);

    PIXI.DisplayObject._tempMatrix.tx = -bounds.x;
    PIXI.DisplayObject._tempMatrix.ty = -bounds.y;

    renderTexture.render(this.phaser, PIXI.DisplayObject._tempMatrix);

    return renderTexture;
};

// 用于渲染用的临时矩阵
Node._snapTempMatrix = new PIXI.Matrix();

/**
 * 生成一张缓存的 RenderTexture
 * @param srcBounds {qc.Rectangle} - 绘制源的绘制区域，默认当前 Node 自身在屏幕中的最终宽高
 * @param dstWidth  {number} - 绘制目标的宽，默认为 srcBound 的宽
 * @param dstHeight {number} - 绘制目标的高，默认为 srcBound 的高
 * @param resolution {Number} - 生成的截图的解析度，默认选择当前游戏的 resolution
 * @return {qc.RenderTexture}
 */
Node.prototype._snapshotAsRenderTexture = function(srcBounds, dstWidth, dstHeight, resolution) {
    // 无效的绘制参数
    if (!this.phaser) {
        return new qc.RenderTexture(0, 0);
    }

    // 计算源的绘制区域
    var bounds = srcBounds;
    var worldScale = this.getWorldScale();

    if (!bounds) {
        bounds = new qc.Rectangle(0,  0, this.width * worldScale.x, this.height * worldScale.y);
    }

    dstWidth = dstWidth || bounds.width;
    dstHeight = dstHeight || bounds.height;

    var drawScaleX = dstWidth / bounds.width;
    var drawScaleY = dstHeight / bounds.height;

    resolution = resolution || this.game.resolution;

    // 目标节点绘制矩阵（只保留缩放，加入偏移，还需要考虑目标画布大小导致的缩放）
    Node._snapTempMatrix.a = worldScale.x * drawScaleX;
    Node._snapTempMatrix.d = worldScale.y * drawScaleY;
    Node._snapTempMatrix.tx = -bounds.x * drawScaleX;
    Node._snapTempMatrix.ty = -bounds.y * drawScaleY;

    var phaserGame = this.game.phaser;

    var renderTexture = new qc.RenderTexture(1, 1, phaserGame.renderer, phaserGame.scale.scaleMode, resolution);
    renderTexture.resize(dstWidth, dstHeight, true);

    // 绘制工作开始吧
    renderTexture.render(this.phaser, Node._snapTempMatrix);

    return renderTexture;
};

/**
 * 生成一张缓存的 image 对象
 * @param srcBounds {qc.Rectangle} - 绘制源的绘制区域，默认当前 Node 自身在屏幕中的最终宽高
 * @param dstWidth  {number} - 绘制目标的宽，默认为 srcBound 的宽
 * @param dstHeight {number} - 绘制目标的高，默认为 srcBound 的高
 * @param resolution {Number} - 生成的截图的解析度，默认选择当前游戏的 resolution
 * @param loadedCallback {function} - Image 成功 loaded 后的回调
 * @return {qc.RenderTexture}
 */
Node.prototype.snapshotAsImage = function(srcBounds, dstWidth, dstHeight, resolution, loadedCallback) {
    var renderTexture = this._snapshotAsRenderTexture(srcBounds, dstWidth, dstHeight, resolution);
    var base64 = renderTexture.getBase64();

    // 可以删除
    renderTexture.destroy(true);

    // 生成贴图对象
    var img = new Image();
    img.src = base64;

    if (loadedCallback) {
        // 关注图片生成
        img.onload = function() {
            loadedCallback(img);
        };
    }

    return img;
};

/**
 * 生成一张缓存的 atlas 对象，可以直接作为 UIImage 的使用
 * @param key {qc.Rectangle} - atlas key，后续可以用 assets.find 来获取到资源
 * @param srcBounds {qc.Rectangle} - 绘制源的绘制区域，默认当前 Node 自身在屏幕中的最终宽高
 * @param dstWidth  {number} - 绘制目标的宽，默认为 srcBound 的宽
 * @param dstHeight {number} - 绘制目标的高，默认为 srcBound 的高
 * @param resolution {Number} - 生成的截图的解析度，默认选择当前游戏的 resolution
 * @return {qc.RenderTexture}
 */
Node.prototype.snapshotAsAtlas = function(key, srcBounds, dstWidth, dstHeight, resolution) {
    // 获取 render texture
    var renderTexture = this._snapshotAsRenderTexture(srcBounds, dstWidth, dstHeight, resolution);

    // 需要删除之前缓存中记录的 key
    var cache = this.game.assets._cache;
    if (cache.checkImageKey(key)) cache.removeImage(key, false);

    // 加入到游戏中
    var atlas = qc.AssetUtil.addAtlasFromImage(this.game, key, key, renderTexture.getCanvas());

    return atlas;
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 序列化支持
 * 当前我们支持游戏数据序列化的类型有：
 * # number - 数字类型
 * # string - 字符串
 * # object - 普通类型的数值对
 * # texture - 贴图数据
 * # prefab - 预制资源(其他序列化后的游戏对象、场景等)
 * # array - 普通类型的数组
 * # prefab[] - 多个预制资源
 * # texture[] - 多个贴图资源
 * # node - 内部的node节点引用
 * # node[] - 内部多个node节点的引用
 * # audio - 音频数据
 *
 * @class qc.Serializer
 * @construct
 */
var Serializer = qc.Serializer = function(game) {
    this.game = game;
};

Serializer.prototype = {};
Serializer.prototype.constructor = Serializer;

/**
 * 序列化时所有支持的字段类型
 */
Serializer.AUTO = 0;
Serializer.INT  = 1;
Serializer.INTS = 2;        // 整数
Serializer.NUMBER  = 3;
Serializer.NUMBERS = 4;     // 数字
Serializer.BOOLEAN  = 5;
Serializer.BOOLEANS = 6;    // 布尔
Serializer.STRING  = 7;
Serializer.STRINGS = 8;     // 字符串
Serializer.MAPPING = 9;     // 数值对，value为字符串
Serializer.TEXTURE  = 10;
Serializer.TEXTURES = 11;   // 图集（贴图）
Serializer.AUDIO  = 12;
Serializer.AUDIOS = 13;     // 音效
Serializer.COLOR  = 14;     // 颜色
Serializer.COLORS = 15;     // 颜色数组
Serializer.PREFAB  = 16;
Serializer.PREFABS = 17;    // 预制
Serializer.NODE  = 18;
Serializer.NODES = 19;      // 场景节点
Serializer.SCRIPT  = 20;
Serializer.SCRIPTS = 21;    // 逻辑脚本
Serializer.GEOM  = 22;
Serializer.GEOMS = 23;      // 几何体，例如：点、线、矩形、圆等
Serializer.POINT  = 24;
Serializer.POINTS = 25;     // 点
Serializer.RECTANGLE  = 26;
Serializer.RECTANGLES = 27; // 矩形
Serializer.CIRCLE  = 28;
Serializer.CIRCLES = 29;    // 圆
Serializer.ELLIPSE  = 30;
Serializer.ELLIPSES = 31;   // 椭圆
Serializer.FONT = 32;       // 字体
Serializer.FONTS = 33;
Serializer.FILTER = 34;
Serializer.FILTERS = 35;    // 着色器
Serializer.TEXTASSET = 36;  // 文本资源
Serializer.TEXTASSETS = 37; // 文本资源数组
Serializer.EXCELASSET = 38; // Excel资源
Serializer.EXCELASSETS = 39; // Excel资源数组

/**
 * 打包一个Node节点
 * context为打包上下文信息
 */
Serializer.prototype.buildBundle = function(ob, context, json) {
    if (!context.dependences)
        // 初始化依赖项
        context.dependences = [];

    var meta = {};
    if (ob.getMeta)
        meta = ob.getMeta();
    json = json || {};
    for (var k in meta) {
        // 逐个字段序列化
        this.toJson(ob, json, context, k, ob[k], meta[k]);
    }

    // 返回object（这里不要打成字符串，免得递归调用时多次序列化，影响效率）
    return {
        class : ob.class,
        data : json,
        __json: ob.__json
    };
};

/**
 * 合并依赖资源(Node节点需要进一步调度，将所有孩子的依赖整合到根节点中)
 */
Serializer.prototype.combineDependence = function(context) {
    var list = {};
    for (var i in context.dependences) {
        var atlas = context.dependences[i];
        if (!list[atlas.uuid])
            list[atlas.uuid] = atlas;
    }
    return list;
};

/**
 * 还原出Node节点，第一遍还原时对于Node节点的引用，都只临时记录其path位置
 * 然后整个节点树构建完毕后再扫描path并赋值
 * awake事件需要在这些事情全部干完后才能派发
 */
Serializer.prototype.restoreBundle = function(json, parent, restoreChild) {
    if (!restoreChild) {
        // 初始化上下文环境
        this.restoreContext = {};
    }

    var uuid = json.data.uuid;
    if (this.game.nodePool.find(uuid))
        uuid = this.game.math.uuid();
    
    var ob = this.newNode(json.class, parent, uuid);
    if (json.__json) ob.__json = deepCopy(json.__json);

    // 逐个字段解析出来
    var meta = {};
    if (ob.getMeta)
        meta = ob.getMeta();
    var data = json.data;
    for (var k in meta) {
        this.fromJson(ob, data, k, meta[k]);
    }

    // 反序列化子孙的话，就不需要后续流程，等待根节点序列化完成后一次性执行就好了
    if (restoreChild) return ob;

    // 序列化Node节点的引用
    ob._restoreNodeRef();

    // 字段序列化完成后初始化一把
    ob._restoreInit();

    // 派发逻辑脚本的awake事件
    ob._dispatchAwake();

    // 派发 enable 事件
    ob._dispatchEnable();

    // 搞定收工
    this.restoreContext = {};
    return ob;
};

/**
 * 还原场景信息
 * @internal
 */
Serializer.prototype.restoreState = function(json) {
    // 先还原出所有子孙
    var arr = [];
    this.restoreContext = {};
    for (var i in json.children) {
        var child = this.restoreBundle(json.children[i], this.game.world, true);
        arr.push(child);
    }

    // 需要在世界节点上处理还原信息
    for (var i in arr) {
        arr[i]._restoreNodeRef();
    }
    for (var i in arr) {
        arr[i]._restoreInit();
    }
    for (var i in arr) {
        arr[i]._dispatchAwake();
    }
    for (var i in arr) {
        arr[i]._dispatchEnable();
    }

    // 清理下
    this.restoreContext = {};
};

/**
 * 序列化一个字段，并存入json中
 * @internal
 */
Serializer.prototype.toJson = function(ob, json, context, key, value, type) {
    type = type || Serializer.AUTO;

    if (typeof type === 'object' && typeof type.get === 'function' && typeof type.set === 'function') {
        // 自定义方法进行序列化
        json[key] = type.get.call(ob, ob, context);
        return;
    }

    switch (type) {
    case Serializer.AUTO:
        type = this.getAutoType(value);
        if (type === undefined) json[key] = value;
        else
            this.toJson(ob, json, context, key, value, type);
        break;

    case Serializer.INT:
    case Serializer.INTS:
    case Serializer.NUMBER:
    case Serializer.NUMBERS:
    case Serializer.STRING:
    case Serializer.STRINGS:
    case Serializer.BOOLEAN:
    case Serializer.BOOLEANS:
    case Serializer.MAPPING:
        // 普通类型的序列化
        this._saveCommonType(ob, json, context, key, value, type);
        break;

    case Serializer.COLOR:
        this.saveColor(ob, json, context, key, value);
        break;
    case Serializer.COLORS:
        this._saveArray(ob, json, context, key, value, type, this._saveColorItem);
        break;

    case Serializer.GEOM:
    case Serializer.POINT:
    case Serializer.RECTANGLE:
    case Serializer.CIRCLE:
    case Serializer.ELLIPSE:
        // GEOM元素的序列化
        this.saveGeom(ob, json, context, key, value);
        break;

    case Serializer.POINTS:
    case Serializer.RECTANGLES:
    case Serializer.CIRCLES:
    case Serializer.ELLIPSES:
        // GEOM元素数组的序列化
        this._saveArray(ob, json, context, key, value, type, this._saveGeomItem);
        break;

    case Serializer.PREFAB:
        this.savePrefab(ob, json, context, key, value);
        break;
    case Serializer.PREFABS:
        this._saveArray(ob, json, context, key, value, type, this._savePrefabItem);
        break;

    case Serializer.TEXTURE:
        this.saveTexture(ob, json, context, key, value);
        break;
    case Serializer.TEXTURES:
        this._saveArray(ob, json, context, key, value, type, this._saveTextureItem);
        break;

    case Serializer.AUDIO:
        this.saveAudio(ob, json, context, key, value);
        break;
    case Serializer.AUDIOS:
        this._saveArray(ob, json, context, key, value, type, this._saveAudioItem);
        break;

    case Serializer.TEXTASSET:
        this.saveTextAsset(ob, json, context, key, value);
        break;
    case Serializer.TEXTASSETS:
        this._saveArray(ob, json, context, key, value, type, this._saveTextAssetItem);
        break;

    case Serializer.EXCELASSET:
        this.saveExcelAsset(ob, json, context, key, value);
        break;
    case Serializer.EXCELASSETS:
        this._saveArray(ob, json, context, key, value, type, this._saveExcelAssetItem);
        break;

    case Serializer.FONT:
        this.saveFont(ob, json, context, key, value);
        break;
    case Serializer.FONTS:
        this._saveArray(ob, json, context, key, value, type, this._saveFontItem);
        break;

    case Serializer.NODE:
        this.saveNode(ob, json, context, key, value);
        break;
    case Serializer.NODES:
        this._saveArray(ob, json, context, key, value, type, this._saveNodeItem);
        break;
    case Serializer.FILTER:
        this._saveFilter(ob, json, context, key, value);
        break;
    case Serializer.FILTERS:
        this._saveArray(ob, json, context, key, value, type, this._saveFilterItem);
        break;
    default:
        throw new Error('unsupported asset type:' + type);
    }
}

/**
 * 反序列化
 * @internal
 */
Serializer.prototype.fromJson = function(ob, json, key, type) {
    type = type || Serializer.AUTO;
    if (typeof type === 'object' && typeof type.get === 'function' && typeof type.set === 'function') {
        // 自定义方法进行序列化
        type.set.call(ob, ob, json[key]);
        return;
    }

    // 先处理NULL的情形
    var value = json[key];
    if (value === undefined) {
        // 没有数据就不处理了
        return;
    }
    if (value === null || value === NaN) {
        // 空值
        ob[key] = value;
        return;
    }

    // 按类型赋值，第一个元素指明了数据类型
    switch (value[0]) {
    case Serializer.INT:
    case Serializer.INTS:
    case Serializer.NUMBER:
    case Serializer.NUMBERS:
    case Serializer.STRING:
    case Serializer.STRINGS:
    case Serializer.BOOLEAN:
    case Serializer.BOOLEANS:
    case Serializer.MAPPING:
        // 普通的类型
        this._restoreCommonType(ob, json, key, value[0]);
        break;
    case Serializer.COLOR:
        this.restoreColor(ob, json, key, value);
        break;
    case Serializer.COLORS:
        this._restoreArray(ob, json, key, value, this._restoreColorItem);
        break;

    case Serializer.GEOM:
    case Serializer.POINT:
    case Serializer.RECTANGLE:
    case Serializer.CIRCLE:
    case Serializer.ELLIPSE:
        // GEOM元素的反序列化
        this.restoreGeom(ob, json, key, value);
        break;

    case Serializer.POINTS:
    case Serializer.RECTANGLES:
    case Serializer.CIRCLES:
    case Serializer.ELLIPSES:
        // GEOM元素数组的序列化
        this._restoreArray(ob, json, key, value, this._restoreGeomItem);
        break;

    case Serializer.PREFAB:
        this.restorePrefab(ob, json, key, value);
        break;
    case Serializer.PREFABS:
        this._restoreArray(ob, json, key, value, this._restorePrefabItem);
        break;

    case Serializer.TEXTURE:
        this.restoreTexture(ob, json, key, value);
        break;
    case Serializer.TEXTURES:
        this._restoreArray(ob, json, key, value, this._restoreTextureItem);
        break;

    case Serializer.EXCELASSET:
        this.restoreExcelAsset(ob, json, key, value);
        break;
    case Serializer.EXCELASSETS:
        this._restoreArray(ob, json, key, value, this._restoreExcelAssetItem);
        break;

    case Serializer.AUDIO:
        this.restoreAudio(ob, json, key, value);
        break;
    case Serializer.AUDIOS:
        this._restoreArray(ob, json, key, value, this._restoreAudioItem);
        break;

    case Serializer.TEXTASSET:
        this.restoreTextAsset(ob, json, key, value);
        break;
    case Serializer.TEXTASSETS:
        this._restoreArray(ob, json, key, value, this._restoreTextAssetItem);
        break;

    case Serializer.FONT:
        this.restoreFont(ob, json, key, value);
        break;
    case Serializer.FONTS:
        this._restoreArray(ob, json, key, value, this._restoreFontItem);
        break;

    case Serializer.NODE:
        this.restoreNode(ob, json, key, value);
        break;
    case Serializer.NODES:
        this.restoreNodes(ob, json, key, value);
        break;
    case Serializer.FILTER:
        this._restoreFilter(ob, json, key, value);
        break;
    case Serializer.FILTERS:
        this._restoreArray(ob, json, key, value, this._restoreFilterItem);
        break;
    default:
        throw new Error('unsupported asset type:' + type);
    }
}

/**
 * base64字符串转普通字符串
 * @param str
 * @returns {string}
 */
Serializer.prototype.atob = function(str) {
    return decodeURIComponent(atob(str));
}

/**
 * 普通字符串转为base64
 * @param base
 */
Serializer.prototype.btoa = function(str) {
    return btoa(encodeURIComponent(str));
};

// 将 Uint8Array 转为 String
Serializer.prototype.unpackUTF8 = function(array) {
    var out, i, len, c;
    var char2, char3;
    var i = 0, len = array.length;

    out = "";
    while(i < len) {
        c = array[i++];
        switch(c >> 4)
        {
        case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
            // 0xxxxxxx
            out += String.fromCharCode(c);
            break;
        case 12: case 13:
            // 110x xxxx   10xx xxxx
            char2 = array[i++];
            out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
            break;
        case 14:
            // 1110 xxxx  10xx xxxx  10xx xxxx
            char2 = array[i++];
            char3 = array[i++];
            out += String.fromCharCode(((c & 0x0F) << 12) |
            ((char2 & 0x3F) << 6) |
            ((char3 & 0x3F) << 0));
            break;
        }
    }

    return out;
};

/**
 * 取得key对应的节点引用
 * @param key
 * @internal
 */
Serializer.prototype.findNodeRef = function(key) {
    var k = this.restoreContext[key] ? this.restoreContext[key] : key;
    return this.game.nodePool.find(k);
};
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 音效元素序列化
 */
Serializer.prototype.saveAudio = function(ob, json, context, key, value) {
    json[key] = this._saveAudioItem(value, context);
}

/**
 * 音效元素反序列化
 */
Serializer.prototype.restoreAudio = function(ob, json, key, value) {
    ob[key] = this._restoreAudioItem(value);
}

/**
 * 序列化数组的一个元素
 * @private
 */
Serializer.prototype._saveAudioItem = function(value, context) {
    if (!(value instanceof qc.SoundAsset)) return null;

    // 记录资源依赖
    context.dependences.push({
        key : value.key,
        uuid : value.uuid
    });

    return [Serializer.AUDIO, value.key, value.uuid];
}

/**
 * 反序列化数组的一个元素
 * @private
 */
Serializer.prototype._restoreAudioItem = function(value) {
    if (!value) return null;

    var asset = this.game.assets.find(value[1]);
    if (!asset)
        asset = this.game.assets.findByUUID(value[2]);
    if (!asset) {
        console.error('音频资源尚未载入, 无法反序列化.', value[1]);
        return null;
    }

    return asset instanceof qc.SoundAsset ? asset : null;
}

/**
 * Created by luohj on 15/6/2.
 */

/**
 * Color元素序列化
 */
Serializer.prototype.saveColor = function(ob, json, context, key, value) {
    json[key] = this._saveColorItem(value);
}

/**
 * Color元素反序列化
 */
Serializer.prototype.restoreColor = function(ob, json, key, value) {
    ob[key] = this._restoreColorItem(value);
}

/**
 * 序列化qc.Color数组的一个元素
 * @private
 */
Serializer.prototype._saveColorItem = function(value) {
    if (!(value instanceof Color)) return null;
    return [Serializer.COLOR, value.toNumber(true)];
}

/**
 * 反序列化Color数组的一个元素
 * @private
 */
Serializer.prototype._restoreColorItem = function(value) {
    if (!value) return null;

    return new Color(value[1]);
}
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * Excel资源序列化
 */
Serializer.prototype.saveExcelAsset = function(ob, json, context, key, value) {
    json[key] = this._saveExcelAssetItem(value, context);
};

/**
 * Excel元素反序列化
 */
Serializer.prototype.restoreExcelAsset = function(ob, json, key, value) {
    ob[key] = this._restoreExcelAssetItem(value);
};

/**
 * 序列化数组的一个Excel资源
 * @private
 */
Serializer.prototype._saveExcelAssetItem = function(value, context) {
    if (!(value instanceof qc.ExcelAsset)) return null;

    // 记录资源依赖
    context.dependences.push({
        key : value.key,
        uuid : value.uuid
    });

    return [Serializer.EXCELASSET, value.key, value.uuid];
};

/**
 * 反序列化数组的一个Excel资源
 * @private
 */
Serializer.prototype._restoreExcelAssetItem = function(value) {
    if (!value) return null;

    var asset = this.game.assets.find(value[1]);
    if (!asset)
        asset = this.game.assets.findByUUID(value[2]);
    if (!asset) {
        return null;
    }

    return asset instanceof qc.ExcelAsset ? asset : null;
};

/**
 * @author luohj
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 字体元素序列化
 */
Serializer.prototype.saveFont = function(ob, json, context, key, value) {
    json[key] = this._saveFontItem(value, context);
}

/**
 * 字体元素反序列化
 */
Serializer.prototype.restoreFont = function(ob, json, key, value) {
    ob[key] = this._restoreFontItem(value);
}

/**
 * 序列化数组的一个元素
 * @private
 */
Serializer.prototype._saveFontItem = function(value, context) {
    if (value instanceof qc.Font) {
        var font = value;
    }

    if (font && font instanceof qc.Font) {
        // 记录资源依赖
        context.dependences.push({
            key : font.key,
            uuid : font.uuid
        });
        var uuid = font.uuid;
        value = font.key;
    }
    return [Serializer.FONT, value, uuid];
}

/**
 * 反序列化数组的一个元素
 * @private
 */
Serializer.prototype._restoreFontItem = function(value, self) {
    if (!value) return null;

    self = self || this;
    var asset = self.game.assets.find(value[1]);
    if (!asset)
        asset = self.game.assets.findByUUID(value[2]);
    if (!asset) {
        asset = value[1];
    }

    return asset;
}
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 给定的元素是不是几何类型
 * @param v
 * @returns {boolean}
 */
Serializer.prototype.isGeom = function(v) {
    return v instanceof qc.Circle ||
        v instanceof qc.Ellipse ||
        v instanceof qc.Line ||
        v instanceof qc.Matrix ||
        v instanceof qc.Polygon ||
        v instanceof qc.Rectangle ||
        v instanceof qc.RoundedRectangle ||
        v instanceof qc.Point;
}

/**
 * 几何元素序列化
 */
Serializer.prototype.saveGeom = function(ob, json, context, key, value) {
    json[key] = this._saveGeomItem(value, context);
}

/**
 * 几何元素反序列化
 */
Serializer.prototype.restoreGeom = function(ob, json, key, value) {
    ob[key] = this._restoreGeomItem(value);
}

/**
 * 序列化数组的一个元素
 * @private
 */
Serializer.prototype._saveGeomItem = function(value, context) {
    var type = Serializer.GEOM;
    if (value instanceof qc.Point)
        type = Serializer.POINT;
    else if (value instanceof qc.Rectangle)
        type = Serializer.RECTANGLE;
    else if (value instanceof qc.Circle)
        type = Serializer.CIRCLE;
    else if (value instanceof qc.Ellipse)
        type = Serializer.ELLIPSE;

    if (value)
        return [type, value.class, value.toJson()];
    else
        return null;
}

/**
 * 反序列化数组的一个元素
 * @private
 */
Serializer.prototype._restoreGeomItem = function(value) {
    if (!value) return null;

    // 第二个元素指明是哪个类（类名）
    var func = qc.Util.findClass(value[1]);
    var geom = new func();
    geom.fromJson(value[2]);
    return geom;
}

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 节点元素序列化
 */
Serializer.prototype.saveNode = function(ob, json, context, key, value) {
    json[key] = this._saveNodeItem(value, context);
}

/**
 * 节点元素反序列化
 */
Serializer.prototype.restoreNode = function(ob, json, key, value) {
    // 场景内其他节点，先只记录其标识符
    ob['__BUILTIN_NODE__' + key] = value[1];
}

/**
 * 反序列化多个节点
 */
Serializer.prototype.restoreNodes = function(ob, json, key, value) {
    // 场景内其他节点，先只记录其标识符
    ob['__BUILTIN_NODE_ARRAY__' + key] = value[1];
}

/**
 * 序列化数组的一个元素
 * @private
 */
Serializer.prototype._saveNodeItem = function(value, context) {
    if (!(value instanceof qc.Node)) return null;

    return [Serializer.NODE, value.uuid];
}

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 预制元素序列化
 */
Serializer.prototype.savePrefab = function(ob, json, context, key, value) {
    json[key] = this._savePrefabItem(value, context);
}

/**
 * 预制元素反序列化
 */
Serializer.prototype.restorePrefab = function(ob, json, key, value) {
    ob[key] = this._restorePrefabItem(value);
}

/**
 * 序列化数组的一个元素
 * @private
 */
Serializer.prototype._savePrefabItem = function(value, context) {
    if (!(value instanceof Prefab)) return null;

    // 记录资源依赖
    context.dependences.push({
        key : value.uuid,
        uuid : value.uuid
    });
    return [Serializer.PREFAB, value.uuid];
}

/**
 * 反序列化数组的一个元素
 * @private
 */
Serializer.prototype._restorePrefabItem = function(value) {
    if (!value) return null;

    var ob = this.game.assets.findByUUID(value[1]);
    return ob instanceof Prefab ? ob : null;
}

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 图集元素序列化
 */
Serializer.prototype.saveTexture = function(ob, json, context, key, value) {
    json[key] = this._saveTextureItem(value, context);
}

/**
 * 图集元素反序列化
 */
Serializer.prototype.restoreTexture = function(ob, json, key, value) {
    ob[key] = this._restoreTextureItem(value);
}

/**
 * 序列化数组的一个元素
 * @private
 */
Serializer.prototype._saveTextureItem = function(value, context) {
    if (!(value instanceof qc.Atlas)) return null;

    // 记录资源依赖
    context.dependences.push({
        key : value.key,
        uuid : value.uuid
    });
    return [Serializer.TEXTURE, value.key, value.uuid];
}

/**
 * 反序列化数组的一个元素
 * @private
 */
Serializer.prototype._restoreTextureItem = function(value) {
    if (!value) return null;

    var atlas = this.game.assets.find(value[1]);
    if (!atlas)
        atlas = this.game.assets.findByUUID(value[2]);
    if (!atlas) {
        console.error('贴图资源尚未载入，无法反序列化。', value[1]);
        return null;
    }
    if (!(atlas instanceof qc.Atlas)) return null;

    return atlas;
}

/**
 * @author linyw
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 文本资源序列化
 */
Serializer.prototype.saveTextAsset = function(ob, json, context, key, value) {
    json[key] = this._saveTextAssetItem(value, context);
};

/**
 * 文本元素反序列化
 */
Serializer.prototype.restoreTextAsset = function(ob, json, key, value) {
    ob[key] = this._restoreTextAssetItem(value);
};

/**
 * 序列化数组的一个文本资源
 * @private
 */
Serializer.prototype._saveTextAssetItem = function(value, context) {
    if (!(value instanceof qc.TextAsset)) return null;

    // 记录资源依赖
    context.dependences.push({
        key : value.key,
        uuid : value.uuid
    });

    return [Serializer.TEXTASSET, value.key, value.uuid];
};

/**
 * 反序列化数组的一个文本资源
 * @private
 */
Serializer.prototype._restoreTextAssetItem = function(value) {
    if (!value) return null;

    var asset = this.game.assets.find(value[1]);
    if (!asset)
        asset = this.game.assets.findByUUID(value[2]);
    if (!asset) {
        console.error('文本资源尚未载入, 无法反序列化.', value[1]);
        return null;
    }

    return asset instanceof qc.TextAsset ? asset : null;
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 序列化与反序列化的一些公共逻辑
 */

/**
 * 自动判定元素序列化的类型
 */
Serializer.prototype.getAutoType = function(v) {
    if (v === undefined || v === null || v === NaN) return undefined;
    if (typeof v === 'number') return Serializer.NUMBER;
    if (typeof v === 'string') return Serializer.STRING;
    if (typeof v === 'boolean') return Serializer.BOOLEAN;
    if (v instanceof qc.Node) return Serializer.NODE;
    if (v instanceof qc.Atlas) return Serializer.TEXTURE;
    if (v instanceof qc.SoundAsset) return Serializer.AUDIO;
    if (v instanceof qc.TextAsset) return Serializer.TEXTASSET;
    if (this.isGeom(v)) return Serializer.GEOM;

    console.error(v);
    throw new Error('');
};

/**
 * 根据类名构造组件
 */
Serializer.prototype.newNode = function(clazz, parent, uuid) {
    switch (clazz) {
    case 'qc.UIImage':
        return this.game.add.image(parent, uuid);
    case 'qc.Sprite':
        return this.game.add.sprite(parent, uuid);
    case 'qc.UIRoot':
        return new qc.UIRoot(this.game, uuid);
    case 'qc.Button':
        return this.game.add.button(parent, uuid);
    case 'qc.Toggle':
        return new qc.Toggle(this.game, parent, uuid);
    case 'qc.UIText':
        return this.game.add.text(parent, uuid);
    case 'qc.Sound':
        return this.game.add.sound(parent, uuid);
    case 'qc.Node':
        return this.game.add.node(parent, uuid);
    case 'qc.ScrollView':
        return this.game.add.scrollView(parent, uuid);
    case 'qc.ScrollBar':
        return this.game.add.scrollBar(parent, false, uuid);
    case 'qc.InputField':
        return this.game.add.inputField(parent, uuid);
    case 'qc.ProgressBar':
        return this.game.add.progressBar(parent, false, uuid);
    case 'qc.Slider':
        return this.game.add.slider(parent, false, uuid);
    case 'qc.Emitter':
        return this.game.add.emitter(parent, uuid);
    case 'qc.Tilemap':
        return this.game.add.tilemap(parent, uuid);
    case 'qc.TileLayer':
        return this.game.add.tileLayer(parent, uuid);
    case 'qc.ObjectLayer':
        return this.game.add.objectLayer(parent, uuid);
    case 'qc.Dom':
        return this.game.add.dom(parent, uuid);
    case 'qc.Graphics':
        return this.game.add.graphics(parent, uuid);
    default:
        throw new Error('unsupported class:' + clazz);
    }
};

/**
 * 序列化普通的类型，直接记录其数据即可
 * @private
 */
Serializer.prototype._saveCommonType = function(ob, json, context, key, value, type) {
    json[key] = [type, value];
};

/**
 * 反序列化普通的类型
 * @private
 */
Serializer.prototype._restoreCommonType = function(ob, json, key, type) {
    // 直接给值
    ob[key] = json[key][1];
};

/**
 * 序列化一个数组，里面的元素交给回调处理
 * @private
 */
Serializer.prototype._saveArray = function(ob, json, context, key, value, type, func) {
    json[key] = [type, []];
    if (!value) return;

    // 逐一元素打包
    for (var i in value) {
        var v = value[i];
        var result = func.call(this, v, context);
        json[key][1].push(result);
    }
};

/**
 * 反序列化一个数组，里面的元素交给回调来反序列化
 * @private
 */
Serializer.prototype._restoreArray = function(ob, json, key, value, func) {
    var self = this;
    var list = [];
    if (value) {
        // 逐一反序列化出来
        for (var i in value[1]) {
            list.push(func.call(this, value[1][i], self));
        }
    }
    ob[key] = list;
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 相机
 *
 * @class qc.Camera
 * @constructor
 * @internal
 */
var Camera = qc.Camera = function(phaser) {
    this.phaser = phaser;
    phaser._qc = this;

    // 该参数Phaser的默认值为true，会导致相机偏移自动取整，
    // 意义不大且会影响编辑工具滚轮缩放，因此默认将其关闭为false。
    phaser.roundPx = false;
}
Camera.prototype = {};
Camera.prototype.constructor = Camera;

Object.defineProperties(Camera.prototype, {
    /**
     * @property {qc.Game} game
     * @readonly
     */
    'game' : {
        get : function() { return this.phaser.game._qc; }
    },

    /**
     * @prooperty {qc.World} world
     * @readonly
     */
    'world' : {
        get : function() { return this.phaser.world._qc; }
    },

    /**
     * @property {number} id - 相机标识（以后多相机可能会用到）
     * @readonly
     */
    'id' : {
        get : function() { return this.phaser.id; }
    },

    /**
     * @property {qc.Rectangle} view - 相机的视野
     * @readonly
     */
    'view' : {
        get : function() { return this.phaser.view; }
    },

    /**
     * @property {qc.Point} postion - 设置相机的位置
     */
    'position' : {
        get : function()  { return this.phaser.position; },
        set : function(v) { this.phaser.position = v;           }
    },

    /**
     * @property {qc.Point} size - 设置相机的视野大小
     */
    'size' : {
        get : function()  { return new qc.Point(this.view.width, this.view.height); },
        set : function(v) { this.setSize(v.x, v.y);                                 }
    },

    /**
     * @property {number} x - 相机的X坐标
     */
    'x' : {
        get : function()  { return this.phaser.x; },
        set : function(v) { this.phaser.x = v;    }
    },

    /**
     * @property {number} y - 相机的Y坐标
     */
    'y' : {
        get : function()  { return this.phaser.y; },
        set : function(v) { this.phaser.y = v;    }
    },

    /**
     * @property {number} width - 相机的视野宽度
     */
    'width' : {
        get : function()  { return this.phaser.width; },
        set : function(v) { this.phaser.width = v;    }
    },

    /**
     * @property {number} height - 相机的视野高度
     */
    'height' : {
        get : function()  { return this.phaser.height; },
        set : function(v) { this.phaser.height = v;    }
    },

    /**
     * @property {qc.Rectangle} bounds
     */
    'bounds' : {
        get : function()  { return this.phaser.bounds; },
        set : function(v) { this.phaser.bounds = v;    }
    },

    /**
     * @property {boolean} visible - 相机是否可见
     */
    'visible' : {
        get : function()  { return this.phaser.visible; },
        set : function(v) { this.phaser.visible = v;    }
    },

    /**
     * @property {qc.Node} target - 相机跟踪的目标节点
     * @readonly
     */
    'target' : {
        get : function()  { return this.phaser.target; }
    }
});

/**
 * 相机跟随目标的方式
 * @constant
 * @type {number}
 */
Camera.FOLLOW_LOCKON = Phaser.Camera.FOLLOW_LOCKON;
Camera.FOLLOW_PLATFORMER = Phaser.Camera.FOLLOW_PLATFORMER;
Camera.FOLLOW_TOPDOWN = Phaser.Camera.FOLLOW_TOPDOWN;
Camera.FOLLOW_TOPDOWN_TIGHT = Phaser.Camera.FOLLOW_TOPDOWN_TIGHT;

/**
 * 跟随目标
 *
 * @method qc.Camera#follow
 * @param {qc.Node} target - 如果设置为null，则停止跟随
 * @param {number} [style] - 跟随目标的方式
 */
Camera.prototype.follow = function(target, style) {
    if (target === null)
        this.phaser.unfollow();
    else
        this.phaser.follow(target, style);
}

/**
 * 相机聚焦到某个点
 * @method qc.Camera#focusOn
 * @param {number} x
 * @param {number} y
 */
Camera.prototype.focusOn = function(x, y) {
    this.phaser.focusOnXY(x, y);
}

/**
 * 重置相机的位置、不跟随等
 * @method qc.Camera#reset
 */
Camera.prototype.reset = function() {
    this.phaser.reset();
}
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * This is where the magic happens. The Game object is the heart of your game,
 * providing quick access to common functions and handling the boot process.
 *
 * "Hell, there are no rules here - we're trying to accomplish something."
 *                                                       Thomas A. Edison
 *
 * @class Phaser.Game
 * @constructor
 * @param {number|string} [width=800] - The width of your game in game pixels. If given as a string the value must be between 0 and 100 and will be used as the percentage width of the parent container, or the browser window if no parent is given.
 * @param {number|string} [height=600] - The height of your game in game pixels. If given as a string the value must be between 0 and 100 and will be used as the percentage height of the parent container, or the browser window if no parent is given.
 * @param {string|HTMLElement} [parent=''] - The DOM element into which this games canvas will be injected. Either a DOM ID (string) or the element itself.
 * @param {object} [state=null] - The default state object. A object consisting of Phaser.State functions (preload, create, update, render) or null.
 * @param {boolean} [transparent=false] - Use a transparent canvas background or not.
 * @param {boolean} [antialias=true] - Draw all image textures anti-aliased or not. The default is for smooth textures, but disable if your game features pixel art.
 * @param {object} [physicsConfig=null] - A physics configuration object to pass to the Physics world on creation.
 */
var Game = qc.Game = function(width, height, parent, state, transparent, editor, debug, physicsConfig) {
    var self = this;

    /**
     * @property {qc.Signal} onStart - 游戏启动成功的事件
     */
    self.onStart = new qc.Signal();

    /**
     * @property {qc.Log} log - 日志系统
     * @readonly
     */
    self.log = new qc.Log();

    // 调试组件
    self._debug = new qc.Debug(self);

    // 设备组件
    self._device = new qc.Device(self, Phaser.Device);

    var config;
    if (typeof arguments[0] === 'object' && arguments.length === 1) {
        // 第一个参数为配置对象
        config = arguments[0];
    }
    else {
        config = {
            width: width,
            height: height,
            parent: parent,
            state: state,
            transparent: transparent,
            physicsConfig: physicsConfig
        };
    }

    self.debug.on = config.debug;
    self.device.editor = config.editor;

    // 附加自己的帧调度等逻辑
    state = config.state;
    state = state || {
        preload : function() {},
        create  : function() {}
    };

    var oldInit = state.init;
    state.init = function() {
        var phaser = self.phaser;

        // 将各组件附加进来
        self._nodePool = new qc.NodePool(self);
        new qc.StateManager(phaser.state);
        new qc.PluginManager(phaser.plugins);
        phaser.math._qc = new qc.Math(phaser.rnd);
        self.timer = new qc.Timer(self);
        self.time = new qc.Time(self);
        self.sound = new qc.SoundManager(self);
        self._assets = new qc.Assets(self, phaser.load, phaser.cache);
        new qc.Stage(phaser.stage);
        new qc.Camera(phaser.camera);
        new qc.World(phaser.world);
        new qc.Input(phaser.input);

        // 设置canvas的边框为0，否则input设置focus时不同浏览器有不同的焦点效果
        self.canvas.style.outline = 0;
        // 设置透明，否则在input.touch.capture为false的情况下点击界面会高亮
        self.container.style.setProperty("-webkit-tap-highlight-color", "rgba(0, 0, 0, 0)", null);

        // 初始化 DragonBones 组件
        self.plugins.add(qc.dragonBonesDriver);

        // PIXI renderer spriteBatch 引用导致内存泄漏，用插件方式修复之
        self.plugins.add(qc.CleanPIXISpriteRetainer);

        // TODO: 其他组件

        // 序列化支持
        self._serializer = new qc.Serializer(self);

        // 本地存储
        self._storage = new qc.Storage(self);

        // 横竖屏检测
        self.phaser.scale.onSizeChange.add(function() {
            // 检查当前的横竖屏
            self.device.resetOrientation();
        });
        self.device.resetOrientation();

        // 调用用户自定义的的初始化
        if (oldInit) {
            oldInit.call(state);
        }

        if (window.qc.initGame) window.qc.initGame(self);

        // 游戏加载成功了
        self.onStart.dispatch();
    };

    config.state = state;
    // 默认使用AUTO渲染
    config.renderer = config.renderer == null ? Phaser.AUTO : config.renderer;

    // WEBGL 默认打开抗锯齿功能, CANVAS 默认关闭抗锯齿
    if (!('antialias' in config)) {
        var antialias;
        switch (config.renderer) {
        case Phaser.HEADLESS:
        case Phaser.CANVAS:   antialias = false; break;
        case Phaser.AUTO:
            var webGLSupport = (function () {
                try {
                    var canvas = document.createElement('canvas');
                    canvas.screencanvas = false;
                    return !!window.WebGLRenderingContext && (canvas.getContext('webgl' )|| canvas.getContext('experimental-webgl'));
                }
                catch(e) {
                    return false;
                }})();
            antialias = (webGLSupport ? true : false);
        default: antialias = true; break;
        }
        config.antialias = antialias;
    }

    // 默认关闭失去焦点不运行功能，外部让用户配置采用runInBackground变量容易理解
    config.disableVisibilityChange = config.runInBackground;
    config.disableVisibilityChange = config.disableVisibilityChange == null ? true : config.disableVisibilityChange;

    if (!config.resolution) {
        config.resolution = window.devicePixelRatio;
    }

    // 默认背景色
    var backgroundColor = config.backgroundColor;
    if(backgroundColor == null) {
        backgroundColor = Color.background; // 默认背景色
    }
    if (backgroundColor instanceof Color) {
        backgroundColor = backgroundColor.toNumber();
    }
    config.backgroundColor = backgroundColor;

    self.phaser = new Phaser.Game(config);
    self.phaser._qc = this;
    this.phaser._calcTransformCount = 0;
    // 设置Game为时间驱动
    self.phaser.forceSingleUpdate = true;

    // 快捷创建node 并添加到父亲上
    self.add = new GameObjectFactory(self);
};

Object.defineProperties(Game.prototype, {
    /**
     * @property {Number} id - 游戏ID
     * @readonly
     */
   'id' : {
       get : function() { return this.phaser.id; }
    },

    /**
     * @property {object} config - 游戏的配置信息
     */
    'config' : {
        get : function()  { return this.phaser.config; },
        set : function(v) { this.phaser.config = v;    }
    },

    /**
     * @property {object} physicsConfig - 游戏的物理配置
     */
    'physicsConfig' : {
        get : function()  { return this.phaser.physicsConfig; },
        set : function(v) { this.phaser.physicsConfig = v;    }
    },

    /**
     * @property {string|HTMLElement} parent - The Games DOM parent.
     * @readonly
     * @default ''
     */
    'parent' : {
        get : function() { return this.phaser.parent; }
    },

    /**
     * @property {integer} width - 当前游戏世界的宽度（单位：像素）
     * @readonly
     * @default 800
     */
    'width' : {
        get : function() { return this.phaser.width; }
    },

    /**
     * @property {integer} height - 当前游戏世界的高度（单位：像素）
     * @readonly
     * @default 600
     */
    'height' : {
        get : function() { return this.phaser.height; }
    },

    /**
     * @property {integer} resolution - 当前游戏的分辨率
     * @readonly
     * @default 1
     */
    'resolution' : {
        get : function()  { return this.phaser.resolution; }
    },

    /**
     * @property {boolean} transparent - 当前游戏画布的背景是不是透明
     * @default false
     */
    'transparent' : {
        get : function()  { return this.phaser.renderer.transparent; },
        set : function(v) { this.phaser.renderer.transparent = v;    }
    },

    /**
     * @property {boolean} isBooted - 游戏是否已经启动
     * @readonly
     */
    'isBooted' : {
        get : function() { return this.phaser.isBooted; }
    },

    /**
     * @property {boolean} isRunning - 当前游戏是不是处于运行状态中
     * @readonly
     */
    'isRunning' : {
        get : function() { return this.phaser.isRunning; }
    },

    /**
     * @property {qc.Camera} camera - 对应的渲染相机
     * @readonly
     */
    'camera' : {
        get : function() { return this.phaser.camera._qc; }
    },

    /**
     * @property {HTMLCanvasElement} canvas - A handy reference to renderer.view, the canvas that the game is being rendered in to.
     * @readonly
     */
    'canvas' : {
        get : function() { return this.phaser.canvas; }
    },

    /**
     * @property {qc.Signal} onPause - 游戏暂停时抛出的事件
     * @readonly
     */
    'onPause' : {
        get : function() { return this.phaser.onPause; }
    },

    /**
     * @property {qc.Signal} onResume - 游戏从暂停回来时抛出的事件
     * @readonly
     */
    'onResume' : {
        get : function() { return this.phaser.onResume; }
    },

    /**
     * @property {qc.Signal} onBlur - 游戏失去焦点时抛出的事件
     * @readonly
     */
    'onBlur' : {
        get : function() { return this.phaser.onBlur; }
    },

    /**
     * @property {qc.Signal} onFocus - 游戏获取焦点时抛出的事件
     * @readonly
     */
    'onFocus' : {
        get : function() { return this.phaser.onFocus; }
    },

    /**
     * @property {qc.StateManager} state
     * @readonly
     */
    'state' : {
        get : function() { return this.phaser.state._qc; }
    },

    /**
     * @property {qc.Stage} stage - 舞台
     * @readonly
     */
    'stage' : {
        get : function() { return this.phaser.stage._qc; }
    },

    /**
     * @property {dom} container - 最外层的dom容器
     * @readonly
     */
    container: {
        get: function() {
            return this.phaser.canvas.parentNode;
        }
    },

    /**
     * @property {qc.Input} input - 交互管理
     * @readonly
     */
    'input' : {
        get : function() { return this.phaser.input._qc; }
    },

    /**
     * @property {qc.PluginManager} plugins - 插件管理
     * @readonly
     */
    'plugins' : {
        get : function() { return this.phaser.plugins._qc; }
    },

    /**
     * @property {qc.Math} math - 数学运算库
     * @readonly
     */
    'math' : {
        get : function() { return this.phaser.math._qc; }
    },

    /**
     * @property {qc.Assets} assets - 资源管理
     * @readonly
     */
    'assets' : {
        get : function() { return this._assets; }
    },

    /**
     * @property {qc.World} world
     * @readonly
     */
    'world' : {
        get : function() {
            if (!this.phaser.world) return null;
            return this.phaser.world._qc;
        }
    },

    /**
     * @property {qc.Serializer} serializer
     * @readonly
     */
    'serializer' : {
        get : function() { return this._serializer; }
    },

    /**
     * @property {qc.NodePool} nodePool
     * @readonly
     * @internal
     */
    'nodePool' : {
        get : function() { return this._nodePool; }
    },

    /**
     * @property {qc.Storage} storage - 本地存储
     * @readonly
     */
    storage : {
        get : function() { return this._storage; }
    },

    /**
     * @property {boolean} paused - 游戏是否暂停
     */
    paused : {
        get : function()  { return this.phaser.paused; },
        set : function(v) {
            this.phaser.paused = v;
        }
    },

    /**
     * @property {number} stepCount - When stepping is enabled this contains the current step cycle.
     * @readonly
     */
    stepCount: {
        get: function() { return this.phaser.stepCount; }
    },

    /**
     * @property {boolean} stepping - Enable core loop stepping with Game.enableStep().
     * @readonly
     */
    stepping: {
        get: function() { return this.phaser.stepping; }
    },

    /**
     * @property {qc.Device} device - 设备组件
     * @readonly
     */
    device: {
        get: function() { return this._device; }
    },

    /**
     * @property {qc.Debug} debug - 调试组件
     * @readonly
     */
    debug: {
        get: function() { return this._debug; }
    }

    // TODO: 其他组件
});

/**
 * 关闭游戏
 * @method qc.Game@shutdown
 */
Game.prototype.shutdown = function() {
    this.input.enable = false;
    this.state.clearWorld(true);
    this.assets.clear();
    this.phaser.destroy();
};

/**
 * Enable core game loop stepping. When enabled you must call game.step() directly (perhaps via a DOM button?)
 * Calling step will advance the game loop by one frame. This is extremely useful for hard to track down errors!
 */
Game.prototype.enableStep = function () {
    this.phaser.enableStep();
};

/**
 * Disables core game loop stepping.
 */
Game.prototype.disableStep = function () {
    this.phaser.disableStep();
};

/**
 * When stepping is enabled you must call this function directly (perhaps via a DOM button?) to advance the game loop by one frame.
 * This is extremely useful to hard to track down errors! Use the internal stepCount property to monitor progress.
 */
Game.prototype.step = function () {
    this.phaser.step();
};

/**
 * 强制更新游戏界面，用于游戏父亲容器改变，需要游戏界面实时同步的情况下调用，
 * Phaser内部canvas大小跟着parent变化并非每帧更新，
 * 受scale.trackParentInterval(2000)参数控制，
 * 通过将scale._lastUpdate设置为0，促发下帧即可更新canvas
 *
 * @param callPreUpdate 是否调用scale.preUpdate()
 */
Game.prototype.updateScale = function(callPreUpdate) {
    var scale = this.phaser.scale;
    if (scale) {
        scale._lastUpdate = 0;
        if (callPreUpdate) {
            scale.preUpdate();
        }
    }
};

/**
 * 全屏显示
 */
Game.prototype.fullScreen = function() {
    var game = this;
    if (game.adjustGameSize) {
        return;
    }

    var lastWidth, lastHeight;
    game.phaser.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;

    // 调整游戏界面大小
    var adjustGameSize = game.adjustGameSize = function(force){
        // 输入状态不调整游戏界面大小
        if (game.isBooted && game.input.inputting) {
            return;
        }

        // 获取当前宽度
        var width = document.documentElement.clientWidth;
        if (window.innerWidth && window.innerWidth < width) {
            width = window.innerWidth;
        }
        // 获取当前宽度
        var height = document.documentElement.clientHeight;
        if (window.innerHeight && window.innerHeight < height) {
            height = window.innerHeight;
        }

        // 没有变化则不处理
        if (lastWidth === width && lastHeight === height && !force) {
            return;
        }
        lastWidth = width;
        lastHeight = height;

        if (game.device.iOS) {
            // 绕开iOS下宽高比未变化界面更新问题
            game.phaser.scale.setGameSize(width-5, height+5);
            game.phaser.time.events.add(1, function() {
                game.phaser.scale.setGameSize(width, height);
            });
        }
        else {
            game.phaser.scale.setGameSize(width, height);
        }
        // 设置富容器大小
        game.container.style.width = width + 'px';
        game.container.style.height = height + 'px';
        game.updateScale();

        // iOS下的UC浏览器输入框隐藏后height有问题，所以...
        if (game.device.phaser.UCBrowser) {
            window.scrollTo(0, 1);
            setTimeout(function(){
                var div = document.createElement('div');
                div.style.position = 'absolute';
                div.style.top = height +'px';
                div.style.width = '100px';
                div.style.height = '100px';
                document.body.appendChild(div);

                window.scrollTo(0, height);
                setTimeout(function(){
                    window.scrollTo(0, 1);
                    document.body.removeChild(div);
                }, 10);
            }, 10);
        }
    };

    var lastCheckTime = game.time.now;
    game._checkFullScreen = function() {
        var time = game.time.now;
        if (time - lastCheckTime < 1000) {
            return;
        }
        adjustGameSize();
        lastCheckTime = time;
    };

    window.addEventListener('orientationchange', adjustGameSize, false);
    window.addEventListener('resize', adjustGameSize, false);
    window.scrollTo(0, 1);
    adjustGameSize();
};

/**
 * 游戏主循环
 * @param time
 */
Game.prototype.update = function(time) {
    var phaser = this.phaser;

    // 检测游戏大小变化
    if (this._checkFullScreen) this._checkFullScreen();

    // phaser的时间系统调度
    phaser._calcTransformCount = 0;

    phaser.time.update(time);
    var fixedFrameDelta = 1.0 / phaser.time.desiredFps;

    if (phaser._kickstart)
    {
        this.updateLogic(fixedFrameDelta);

        //  Sync the scene graph after _every_ logic update to account for moved game objects
        phaser.stage.updateTransform();

        // call the game render update exactly once every frame
        this.updateRender(phaser.time.slowMotion * phaser.time.desiredFps);

        phaser._kickstart = false;
        return;
    }

    // if the logic time is spiraling upwards, skip a frame entirely
    if (phaser._spiraling > 1 && !phaser.forceSingleUpdate)
    {
        // cause an event to warn the program that this CPU can't keep up with the current desiredFps rate
        if (phaser.time.time > phaser._nextFpsNotification)
        {
            // only permit one fps notification per 10 seconds
            phaser._nextFpsNotification = phaser.time.time + 1000 * 10;

            // dispatch the notification signal
            phaser.fpsProblemNotifier.dispatch();
        }

        // reset the _deltaTime accumulator which will cause all pending dropped frames to be permanently skipped
        phaser._deltaTime = 0;
        phaser._spiraling = 0;

        // call the game render update exactly once every frame
        phaser.updateRender(phaser.time.slowMotion * phaser.time.desiredFps);
    }
    else
    {
        // step size taking into account the slow motion speed
        var slowStep = phaser.time.slowMotion * 1000.0 / phaser.time.desiredFps;

        // accumulate time until the slowStep threshold is met or exceeded... up to a limit of 3 catch-up frames at slowStep intervals
        phaser._deltaTime += Math.max(Math.min(slowStep * 3, phaser.time.elapsed), 0);

        // call the game update logic multiple times if necessary to "catch up" with dropped frames
        // unless forceSingleUpdate is true
        var count = 0;

        phaser.updatesThisFrame = Math.floor(phaser._deltaTime / slowStep);

        if (phaser.forceSingleUpdate)
        {
            phaser.updatesThisFrame = Math.min(1, phaser.updatesThisFrame);
        }

        var needToUpdate = false;
        while (phaser._deltaTime >= slowStep)
        {
            phaser._deltaTime -= slowStep;
            phaser.currentUpdateID = count;

            if (!needToUpdate) {
                // 如果处于暂停状态也不进行更新
                if (!this._paused && !this.pendingStep)
                {
                    needToUpdate = true;
                }
            }
            if (needToUpdate) {
                phaser.updateFrameDelta();
                this.updateLogic(fixedFrameDelta);

                //  Sync the scene graph after _every_ logic update to account for moved game objects
                phaser.stage.updateTransform();
            }

            count++;

            if (phaser.forceSingleUpdate && count === 1)
            {
                break;
            }
        }

        // detect spiraling (if the catch-up loop isn't fast enough, the number of iterations will increase constantly)
        if (count > phaser._lastCount)
        {
            phaser._spiraling++;
        }
        else if (count < phaser._lastCount)
        {
            // looks like it caught up successfully, reset the spiral alert counter
            phaser._spiraling = 0;
        }

        phaser._lastCount = count;

        // call the game render update exactly once every frame unless we're playing catch-up from a spiral condition
        if (needToUpdate)
            this.updateRender(phaser._deltaTime / slowStep);
        return needToUpdate;
    }
};

/**
 * 逻辑调度
 */
Game.prototype.updateLogic = function(timeStep) {
    var t1 = Date.now();
    var phaser = this.phaser;

    if (!phaser._paused && !phaser.pendingStep)
    {
        // 定时器调度
        if (this.timer)
            this.timer.update(this.time.now);

        if (phaser.stepping)
        {
            phaser.pendingStep = true;
        }

        var now = Date.now();
        phaser.scale.preUpdate();
        phaser.debug.preUpdate();
        phaser.world.camera.preUpdate();
        phaser.physics.preUpdate();
        phaser.state.preUpdate(timeStep);
        phaser.plugins.preUpdate(timeStep);
        phaser.stage.preUpdate();
        this.debug.preUpdate += Date.now() - now;
        now = Date.now();

        phaser.state.update();
        phaser.stage.update();
        phaser.tweens.update(timeStep);
        phaser.sound.update();
        phaser.input.update();
        phaser.physics.update();
        phaser.particles.update();
        phaser.plugins.update();
        this.debug.update += this.time.now - now;
        now = Date.now();

        phaser.stage.postUpdate();
        phaser.plugins.postUpdate();
        this.debug.postUpdate += Date.now() - now;
        now = Date.now();
    }
    else
    {
        // Scaling and device orientation changes are still reflected when paused.
        phaser.scale.pauseUpdate();
        phaser.state.pauseUpdate();
        phaser.debug.preUpdate();
    }

    this.debug.logic += Date.now() - t1;
};

/**
 * 渲染调度
 */
Game.prototype.updateRender = function(elapsedTime) {
    var t1 = Date.now();
    var phaser = this.phaser;

    if (phaser.lockRender)
    {
        return;
    }

    phaser.state.preRender(elapsedTime);
    phaser.renderer.render(phaser.stage);
    phaser.plugins.render(elapsedTime);
    phaser.state.render(elapsedTime);
    phaser.plugins.postRender(elapsedTime);

    // 缓存池更新
    qc.CanvasPool.postRender();

    this.debug.render += Date.now() - t1;
};

/**
 * @author wudm
 * copyright 2015 Qcplay All Rights Reserved.
 */


/**
 * Called by the Stage visibility handler.
 *
 * @method Phaser.Game#gamePaused
 * @param {object} event - The DOM event that caused the game to pause, if any.
 * @protected
 */
Phaser.Game.prototype.gamePaused = function (event) {

    //   If the game is already paused it was done via game code, so don't re-pause it
    if (!this._paused)
    {
        this._paused = true;
        this.time.gamePaused();
        this.sound.setMute();
        this.onPause.dispatch(event);

        //  Avoids Cordova iOS crash event: https://github.com/photonstorm/phaser/issues/1800
        if (this.device.cordova && this.device.iOS)
        {
            this.lockRender = true;
        }
    }

};

/**
 * Called by the Stage visibility handler.
 *
 * @method Phaser.Game#gameResumed
 * @param {object} event - The DOM event that caused the game to pause, if any.
 * @protected
 */
Phaser.Game.prototype.gameResumed = function (event) {

    //  Game is paused, but wasn't paused via code, so resume it
    if (this._paused && !this._codePaused)
    {
        this._paused = false;
        this.time.gameResumed();
        this.input.reset();
        this.sound.unsetMute();
        this.onResume.dispatch(event);

        //  Avoids Cordova iOS crash event: https://github.com/photonstorm/phaser/issues/1800
        if (this.device.cordova && this.device.iOS)
        {
            this.lockRender = false;
        }
    }

};

// TODO: hack Phaser.Game 中 parseConfig 方法，其 antialias 默认值为 true，且判断是写成
//       if (config['antialias']) ...  导致我们 config 中的 antialias 设置为 false 不被接受
/**
 * Parses a Game configuration object.
 *
 * @method Phaser.Game#parseConfig
 * @protected
 */
var phaserGameParseConfig = Phaser.Game.prototype.parseConfig;
Phaser.Game.prototype.parseConfig = function(config) {
    // 返回原有函数进行继续处理
    phaserGameParseConfig.call(this, config);

    // hack start
    // 设置 antialias
    if ('antialias' in config) {
        this.antialias = !!config['antialias'];
    }
    // hack end
};

// hack Phaser.RequestAnimationFrame
// 原版中主循环调度没有捕获错误，这里加上
var phaser_updateSetTimeout = Phaser.RequestAnimationFrame.prototype.updateSetTimeout;
Phaser.RequestAnimationFrame.prototype.updateSetTimeout = function() {
    try {
        phaser_updateSetTimeout.call(this);
    }
    catch (e) {
        this.game._qc.log.error('Error：{0}', e);
    }
};
var phaser_updateRAF = Phaser.RequestAnimationFrame.prototype.updateRAF;
Phaser.RequestAnimationFrame.prototype.updateRAF = function(rafTime) {
    try {
        phaser_updateRAF.call(this, rafTime);
    }
    catch (e) {
        this.game._qc.log.error('Error：{0}', e);
    }
};

// 用来提供实际每帧的间隔
Phaser.Game.prototype.updateFrameDelta = function(fixedFrameDelta) {
    var self = this,
        time = self.time;
    var currTime = time.time;
    var diff = self.__lastFrameTime ? (currTime - self.__lastFrameTime) : fixedFrameDelta;
    self.__lastFrameTime = currTime;
    time.frameDeltaTime = diff / time.slowMotion;
    time._totalEscapeTime = time.frameDeltaTime + (time._totalEscapeTime || 0);
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 自定义插件的基类模板
 *
 * @class qc.Plugin
 * @param {qc.Game} game
 * @param {any} owner - 谁来管理这个插件？通常为：qc.PluginManager
 * @constructor
 */
var Plugin = qc.Plugin = Phaser.Plugin;

// 看起来没有需要额外定制的，直接使用（减少PluginManager的封装工作）

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 管理运行所有的自定义系统插件
 *
 * @class qc.PluginManager
 * @param {Phaser.PluginManager} phaser
 * @constructor
 * @internal
 */
var PluginManager = qc.PluginManager = function(phaser) {
    // 建立下关联
    phaser._qc = this;
    this.phaser = phaser;
}
PluginManager.prototype = {};
PluginManager.prototype.constructor = PluginManager;

Object.defineProperties(PluginManager.prototype, {
    /**
     * @property {qc.Game} game
     * @readonly
     */
   'game' : {
       get : function() { return this.phaser.game._qc; }
   },

    /**
     * @property {qc.Plugin[]} plugins - 所有的插件列表
     * @readonly
     */
    'plugins' : {
        get : function() { return this.phaser.plugins; }
    }
});

/**
 * 添加一个插件
 *
 * @method qc.PluginManager#add
 * @param {object|qc.Plugin} plugin - 待添加的插件
 * @pram {...*} parameter - 额外参数，在调用插件的init时原样传入
 */
PluginManager.prototype.add = function(plugin) {
    var plugin = this.phaser.add.apply(this.phaser, arguments);
    plugin.game = this.game;
    return plugin;
}

/**
 * 移除一个插件
 *
 * @method qc.PluginManager#remove
 * @param {qc.Plugin} plugin - 待移除的插件
 */
PluginManager.prototype.remove = function(plugin) {
    this.phaser.remove(plugin);
}

/**
 * 移除所有的插件
 *
 * @method qc.PluginManager#removeAll
 */
PluginManager.prototype.removeAll = function() {
    this.phaser.removeAll();
}

/**
 * 析构插件管理器
 *
 * @method qc.PluginManager#destroy
 */
PluginManager.prototype.destroy = function() {
    this.phaser.destroy();
    delete this.phaser;
}

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

// 直接使用吧
qc.Signal = Phaser.Signal;
qc.SignalBinding = Phaser.SignalBinding;

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 舞台，控制所有游戏元素的根节点。
 *
 * @class qc.Stage
 * @param {Phaser.Stage} phaser
 * @internal
 * @constructor
 */
var Stage = qc.Stage = function(phaser) {
    this.phaser = phaser;
    phaser._qc = this;
};

Stage.prototype = {};
Stage.prototype.constructor = Stage;

Object.defineProperties(Stage.prototype, {
    /**
     * @proeprty {qc.Game} game
     * @readonly
     */
    'game' : {
        get : function() { return this.phaser.game._qc; }
    },

    /**
     * @property {String} name - 舞台标识
     * @default '_stage_root'
     */
    'name' : {
        get : function()  { return this.phaser.name; },
        set : function(v) { this.phaser.name = v;    }
    },

    /**
     * property {boolean} runInBackground - 是不是在后台运行
     * @default true
     */
    runInBackground : {
        get : function()  { return this.phaser.disableVisibilityChange; },
        set : function(v) { this.phaser.disableVisibilityChange = v;    }
    },


    /**
     * 背景颜色
     * @property {qc.Color} backgroundColor
     */
    'backgroundColor' : {
        get : function()  { return new Color(this.phaser.backgroundColor); },
        set : function(value) {
            value = value || Color.background;
            if (!(value instanceof Color))
                throw new Error('Expected qc.Color');
            this.phaser.backgroundColor = value.toNumber();
        }
    },

    /**
     * @property {number} x
     * 本地X坐标，永远为0
     * @readonly
     * @override
     */
    'x': {
        get: function() {
            return 0;
        }
    },

    /**
     * @property {number} x
     * 本地X坐标，永远为0
     * @readonly
     * @override
     */
    'y': {
        get: function() {
            return 0;
        }
    },

    /**
     * @property {qc.Rectangle} rect - 矩形框(相对于父亲节点)
     * @readonly
     * @override
     */
    'rect' : {
        get : function() {
            return new qc.Rectangle(0, 0, this.game.width, this.game.height);
        }
    },

    /**
     * @property {number} pivotX - 节点自身的原点X位置
     * @override
     * @readonly
     */
    pivotX : {
        get : function() { return 0; },
        set : function(v) { throw new Error('pivotX cannot be modified'); }
    },

    /**
     * @property {number} pivotY - 节点自身的原点Y位置
     * @override
     * @readonly
     */
    pivotY : {
        get : function() { return 0; },
        set : function(v) { throw new Error('pivotY cannot be modified'); }
    },

    /**
     * @property {qc.Matrix} worldTransform - 自身在世界的变形矩阵
     * @protected
     * @readonly
     */
    worldTransform : {
        get : function() {
            return this.phaser.worldTransform;
        }
    }
});

/**
 * 更新列表中所有的transforms
 *
 * @method qc.Stage#updateTransform
 */
Stage.prototype.updateTransform = function() {
    this.phaser.updateTransform();
}

/**
 * 销毁舞台
 *
 * @method qc.Stage#destroy
 */
Stage.prototype.destroy = function() {
    this.phaser.destroy();
    delete this.phaser;
}

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 场景的基类
 * 同时提供了一些常用功能的快速访问接口
 *
 * @class qc.State
 * @constructor
 */
var State = qc.State = function() {
    // 建立代理对象的关联
    var phaser = new Phaser.State();
    this.phaser = phaser;
    phaser._qc = this;
}
State.prototype = {};
State.prototype.constructor = State;

Object.defineProperties(State.prototype, {
    /**
     * @property {qc.Game} game - 游戏实例的引用
     * @readonly
     */
    'game' : {
        get : function() { return this.phaser.game; }
    },

    /**
     * @property {String} key - 场景的唯一标识符
     */
    'key' : {
        get : function()  { return this.phaser.key; },
        set : function(v) { this.phaser.key = v; }
    },

    /**
     * @property {qc.GameObjectFactory} add
     */
    'add' : {
        get : function() { return this.phaser.add._qc; },
        set : function(v) {
            this.phaser.add = v.phaser;
            v.phaser._qc = v;
        }
    },

    /**
     * @property {qc.GameObjectCreator} make
     */
    'make' : {
        get : function()  { return this.phaser.make._qc; },
        set : function(v) {
            this.phaser.make = v.phaser;
            v.phaser._qc = v;
        }
    },

    /**
     * @property {qc.Camera} camera
     */
    'camera' : {
        get : function() { return this.phaser.camera._qc; },
        set : function(v) {
            this.phaser.camera = v.phaser;
            v.phaser._qc = v;
        }
    },

    /**
     * @property {qc.Assets} assets - 资源管理接口
     */
    'assets' : {
        get : function()  { return this._assets; },
        set : function(v) { this._assets = v; }
    },

    /**
     * @property {qc.Input} input - 输入管理
     */
    'input' : {
        get : function()  { return this.phaser.input._qc; },
        set : function(v) {
            this.phaser.input = v.phaser;
            v.phaser._qc = v;
        }
    },

    /**
     * @property {qc.Math} math - 数学相关运算库
     */
    'math' : {
        get : function()  { return this.phaser.math._qc; },
        set : function(v) {
            this.phaser.math = v.phaser;
            v.phaser._qc = v;
        }
    },

    /**
     * @property {qc.Time} time - 时间管理
     */
    'time' : {
        get : function()  { return this.phaser.time._qc; },
        set : function(v) {
            this.phaser.time = v.phaser;
            v.phaser._qc = v;
        }
    },

    /**
     * @property {qc.TweenManager} tweens - 动画组件
     */
    'tweens' : {
        get : function()  { return this.phaser.tweens._qc; },
        set : function(v) {
            this.phaser.tweens = v.phaser;
            v.phaser._qc = v;
        }
    },

    /**
     * @property {qc.World} world - 对应的游戏世界
     */
    'world' : {
        get : function()  {
            if (!this.phaser.world) return null;
            return this.phaser.world._qc;
        },
        set : function(v) {
            this.phaser.world = v.phaser;
            v.phaser._qc = v;
        }
    },

    /**
     * @property {qc.Particles} particles - 使用的粒子系统
     */
    'particles' : {
        get : function()  { return this.phaser.particles._qc; },
        set : function(v) {
            this.phaser.particles = v.phaser;
            v.phaser._qc = v;
        }
    },

    /**
     * @property {qc.Physics} physics - 使用的物理系统
     */
    'physics' : {
        get : function()  { return this.phaser.physics._qc; },
        set : function(v) {
            this.phaser.physics = v.phaser;
            v.phaser._qc = v;
        }
    }
});

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 多个场景的管理，提供了如下操作：
 * 1、场景的载入
 * 2、场景的配置
 * 3、场景的切换等
 *
 * @class qc.StateManager
 * @constructor
 * @internal
 */
var StateManager = qc.StateManager = function(phaser) {
    phaser._qc = this;
    this.phaser = phaser;

    this._onPreload = new qc.Signal();
    this._onCreate = new qc.Signal();

    /**
     * @property {qc.Signal} onStartLoad - 场景开始加载的事件
     */
    this.onStartLoad = new qc.Signal();

    /**
     * @property {qc.Signal} onEndLoad - 场景加载完成的事件
     */
    this.onEndLoad = new qc.Signal();
};
StateManager.prototype = {};
StateManager.prototype.constructor = StateManager;

Object.defineProperties(StateManager.prototype, {
    /**
     * @property {qc.Game} game - 游戏实例的引用
     * @readonly
     */
    game : {
        get : function() { return this.phaser.game._qc; }
    },

    /**
     * @property {string} entry - 用户的入口场景
     */
    entry : {
        get : function()  { return this._entry; },
        set : function(v) { this._entry = v;    }
    },

    /**
     * @property {object} list - 用户的场景配置，key为场景名字，value为场景的序列化文件URL
     */
    list : {
        get : function()  { return this._list; },
        set : function(v) {
            this._list = v;

            // 构造场景对象并加入
            var self = this;
            for (var k in v) {
                var state = {
                    preload : function() {
                        // 扔出事件
                        self._onPreload.dispatch();

                        // 加载依赖资源
                        var asset = self.game.assets.find(self.list[self.current]);
                        // 如果加载失败会导致asset为空
                        if (asset) {
                            for (var i in asset.dependences) {
                                var data = asset.dependences[i];
                                self.game.assets.loadByUUID(data.uuid, function(a) {
                                    if (!a)
                                    {
                                        self.game.log.important('场景的依赖资源({0})加载失败.', data.uuid);
                                        // 场景依赖资源加载失败，也通知加载进度
                                        if (typeof(self.game.loadingProcessCallback) == 'function')
                                        {
                                            // 预制中的资源不通加进度，整个预制加载成功通知一次即可
                                            self.game.loadingProcessCallback();
                                        }
                                    }
                                });
                            }
                        }
                    },
                    create : function() {
                        try {
                            // 解析场景的配置信息，并构建必要的对象
                            self._parse();
                        }
                        catch (e)
                        {
                            self.game.log.error('Parse scene fail：{0}', e);
                            qc.Util.popupError(e.message); 
                        }

                        // 标记当前没有加载场景了
                        self.loading = false;

                        // 扔出场景加载完毕的事件
                        self._onCreate.dispatch();
                        self.onEndLoad.dispatch(state);
                    }
                };
                this.phaser.add(k, state, false);
            }
        }
    },

    /**
     * @property {boolean} loading - 当前是否正在加载场景中
     * @default false
     */
    loading : {
        get : function()  { return this._loading || false; },
        set : function(v) { this._loading = v;             }
    },

    /**
     * @property {string} current - 当前加载中或运行中的场景名称
     * @readonly
     */
    current : {
        get : function() { return this.phaser.current; }
    }
});

/**
 * 预加载场景的序列化文件
 *
 * @method qc.StateManager#download
 * @param state {string|undefined} - 待加载的场景名称，如果没有指定默认加载入口场景
 * @param callback {undefined|function} - 加载完成后的回调
 */
StateManager.prototype.download = function(state, callback) {
    state = state || this.entry;
    var url = this.list[state];

    this.game.log.trace('Start downloading scene:{0}', state);
    this.game.assets.load(url, url, callback);
};

/**
 * 切换场景
 *
 * @method qc.StateManager#load
 * @param state {string|undefined} - 待切换的目标场景，如果没有指定默认加载入口场景
 * @param clear {boolean} - 是否把所有缓存的资源清理掉，默认为FALSE
 * @param preload {undefined|function} 场景加载回调
 * @param create {undefined|function} 场景创建回调
 */
StateManager.prototype.load = function(state, clear, preload, create) {
    // 场景的切换应该是互斥的
    var self = this;
    if (self.loading) {
        console.warn('Cannot call when switching scene.');
        return;
    }

    state = state || self.entry;
    if (clear !== true) clear = false;

    if (!self.list[state]) {
        self.game.log.error('Scene:{0} not exists', state);
        return;
    }

    self.loading = true;

    // 扔出事件
    self.onStartLoad.dispatch(state);

    // 清理下旧场景的节点
    self.clearWorld();
    if (clear) self.game.assets.clear();

    // 同步下载场景的内容
    self.download(state, function(data) {
        // 关注下场景的切换事件
        if (typeof preload === "function")
            self._onPreload.addOnce(preload);
        if (typeof create === "function")
            self._onCreate.addOnce(create);

        // 通过phaser来启动场景
        self.phaser.start(state, false, clear);
    });
};

/**
 * 清理场景的内容
 * @param shutDown 默认为false，如果非shutDown则一般为切换场景时调用，切换场景时保留ignoreDestroy的图元
 */
StateManager.prototype.clearWorld = function(shutDown) {
    var children = this.game.world.children.slice(0);
    children.forEach(function(child) {
        if (shutDown || !child.ignoreDestroy) {
            child.destroy();
        }
    });
};

/**
 * 析构整个场景，并回收相关的所有资源
 *
 * @method qc.StateManager#destroy
 */
StateManager.prototype.destroy = function() {
    this.entry = undefined;
    this.list = {};
    this.phaser.destroy();
};

/**
 * 反序列化场景信息
 * @private
 */
StateManager.prototype._parse = function() {
    var key = this.list[this.current];
    var asset = this.game.assets.find(key);
    // 如果资源加载失败asset会为空
    if (asset) {
        var json = asset.json.data;
        this.game.serializer.restoreState(json);
        this.game.world._prefab = key;
    }
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * A game has only one world. The world is an abstract place in which all game objects live. It is not bound
 * by stage limits and can be any size. You look into the world via cameras. All game objects live within
 * the world at world-based coordinates. By default a world is created the same size as your Stage.
 *
 * @class qc.World
 * @param {Phaser.World} phaser
 * @internal
 * @constructor
 */
var World = qc.World = function(phaser) {
    var self = this;

    // 用于派发模型变化事件，如名称变化，父子关系变化，孩子销毁等。
    self.onModelChange = new qc.Signal();

    // 调用基类初始化
    qc.Node.call(self, phaser, self);

    // 设置节点的名字
    self.name = 'world';

    // 宽高改变时需要重新relayout
    self.onSizeChange.add(function() {
        self.relayout();
    });

    // 待删除的节点列表
    self._toDestroyQ = [];

    /**
     * property {Dom} backDomRoot - 处于底层的Dom根节点
     * @readonly
     */
    self.game.container.style.overflow = 'hidden';
    self.backDomRoot = document.createElement('div');
    self.backDomRoot.style.left = '0px';
    self.backDomRoot.style.top = '0px';
    self.game.container.insertBefore(self.backDomRoot, self.game.canvas);

    /**
     * property {Dom} frontDomRoot - 处于上层的Dom根节点
     * @readonly
     */
    self.frontDomRoot = document.createElement('div');
    self.frontDomRoot.style.left = '0px';
    self.frontDomRoot.style.top = '0px';
    self.game.container.appendChild(self.frontDomRoot);
    self._resetDomSize();
};

World.prototype = Object.create(qc.Node.prototype);
World.prototype.constructor = World;

/**
 * 需要序列化的字段和类型
 * @internal
 */
World.prototype.getMeta = function() {
    var s = qc.Serializer;
    var json = qc.Node.prototype.getMeta.call(this);

    // World节点的uuid不需要存储
    delete json['uuid'];

    return json;
};

// 重载World的relayout函数，不对自己进行布局，只对孩子进行布局
World.prototype.relayout = function() {
    // 所有的孩子也要进行重排
    var children = this.children;
    for (var i = 0; i < children.length; i++) {
        children[i].relayout();
    }
};

Object.defineProperties(World.prototype, {
    /**
     * The World has no fixed size, but it does have a bounds outside of which objects are no longer considered as
     * being "in world" and you should use this to clean-up the display list and purge dead objects.
     *
     * By default we set the Bounds to be from 0,0 to Game.width,Game.height.
     * I.e. it will match the size given to the game constructor with 0,0 representing the top-left of the display.
     *
     * However 0,0 is actually the center of the world, and if you rotate or scale the world all of that will happen from 0,0.
     * So if you want to make a game in which the world itself will rotate you should adjust the bounds
     * so that 0,0 is the center point, i.e. set them to -1000,-1000,2000,2000 for a 2000x2000 sized world centered around 0,0.
     *
     * @property {qc.Rectangle} bounds - Bound of this world that objects can not escape from.
     */
    'bounds' : {
        get : function()  { return this.phaser.bounds; },
        set : function(v) { this.phaser.setBounds(v.x, v.y, v.width, v.height); }
    },

    /**
     * @property {boolean} camera
     * @readonly
     */
    'camera' : {
        get : function()  { return this.phaser.camera; }
    },

    /**
     * @property {number} width - 设置游戏世界的宽度
     * @override
     */
    'width' : {
        get : function() {
            // 在编辑器模式下，获取编辑器设置的大小
            if (this._editorWidth !== undefined)
                return this._editorWidth;
            return this.phaser.width;
        },
        set : function(v) {
            if (this.width === v) return;
            this.phaser.width = v;
            Object.getOwnPropertyDescriptor(qc.Node.prototype, 'width').set.call(this, v);
        }
    },

    /**
     * @property {number} height - 设置游戏世界的高度
     * @override
     */
    'height' : {
        get : function()
        {
            // 在编辑器模式下，获取编辑器设置的大小
            if (this._editorHeight !== undefined)
                return this._editorHeight;
            return this.phaser.height;
        },
        set : function(v) {
            if (this.height === v) return;
            this.phaser.height = v;
            Object.getOwnPropertyDescriptor(qc.Node.prototype, 'height').set.call(this, v);
        }
    },

    /**
     * @property {number} centerX - 中心点X坐标
     * @readonly
     */
    'centerX' : {
        get : function() { return this.phaser.centerX; }
    },

    /**
     * @property {number} centerY - 中心点Y坐标
     * @readonly
     */
    'centerY' : {
        get : function() { return this.phaser.centerY; }
    },

    /**
     * @property {number} pivotX - 节点自身的原点X位置
     * @override
     * @readonly
     */
    pivotX : {
        get : function() { return 0; },
        set : function(v) { throw new Error('pivotX cannot be modified'); }
    },

    /**
     * @property {number} pivotY - 节点自身的原点Y位置
     * @override
     * @readonly
     */
    pivotY : {
        get : function() { return 0; },
        set : function(v) { throw new Error('pivotY cannot be modified'); }
    },

    /**
     * @property {qc.Signal} onSizeChange - 大小发生变化的事件
     * @readonly
     */
    onSizeChange: {
        get: function() { return this.game.phaser.scale.onSizeChange; }
    }
});

/**
 * 帧调度：preUpdate
 * @internal
 */
World.prototype.preUpdate = function() {
    // 清理下需要被析构的节点
    this._toDestroyQ.forEach(function(node) {
        node.destroyImmediately();
    });
    this._toDestroyQ.length = 0;
};

/**
 * 添加待删除的节点
 * @param node
 * @internal
 */
World.prototype.addToDestroy = function(node) {
    this._toDestroyQ.push(node);
};

/**
 * 重新设置下Dom的位置和大小
 * @private
 */
World.prototype._resetDomSize = function() {
    var self = this;
    [self.backDomRoot, self.frontDomRoot].forEach(function(div) {
        div.style.position = 'absolute';
        div.style.overflow = 'hidden';
        div.style.width = "100%";
        div.style.height = "100%";
    });
};

/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 输入交互类，管理Game界面的键盘、鼠标和手指的操作事件。
 *
 * @class qc.Input
 * @param {Phaser.Input} phaser
 * @param {DOM} listenerDOM - 监听事件的DOM元素，默认为window
 * @constructor
 */
var Input = qc.Input = function(phaser) {
    var self = this;

    phaser._qc = self;

    // 替换destroy
    var oldDestroy = phaser.destroy;
    phaser.destroy = function() {
        self.destroy();
        oldDestroy.call(phaser);
    };

    // 替换update
    var oldUpdate = phaser.update;
    phaser.update = function() {
        self.update();
        oldUpdate.call(phaser);
    };

    /**
     * @property {Phaser.Input} phaser - phaser输入对象
     */
    self.phaser = phaser;

    /**
     * @property {qc.Game} game - game对象
     */
    self.game = phaser.game._qc;

    // 是否正在输入计数器
    self._inputting = 0;    

    /**
     * @property {qc.Point} cursorPosition - 当前光标在世界坐标系中的位置，只有鼠标移动会影响该值
     * 当isNaN(cursorPosition.x)或者isNaN(cursorPosition.y)时，光标信息无效
     */
    self.cursorPosition = new qc.Point(NaN, NaN);

    /**
     * @property {Phaser.Signal} onKeyDown - 按键按下的事件派发器，派发键盘，手柄等的按键事件
     * 派发参数：keyCode
     */
    self.onKeyDown = new Phaser.Signal();

    /**
     * @property {Phaser.Signal} onKeyUp - 按键弹起的事件派发器，派发键盘，手柄等的按键事件
     * 派发参数：keyCode
     */
    self.onKeyUp = new Phaser.Signal();

    /**
     * @property {Phaser.Signal} onKeyRepeat - 按键重复的事件派发器，派发键盘，手柄等的按键事件
     * 派发参数：keyCode
     */
    self.onKeyRepeat = new Phaser.Signal();

    /**
     * @property {Phaser.Signal} onCursorMove - 光标移动事件派发器
     * 派发参数：x, y
     */
    self.onCursorMove = new Phaser.Signal();

    /**
     * @property {Phaser.Signal} onWheel - 滚动事件派发
     * 派发参数：deltaX, deltaY
     */
    self.onWheel = new Phaser.Signal();

    /**
     * @property {Phaser.Signal} onPointerUp - 点事件派发，派发鼠标弹起，触摸结束等点事件
     * 派发参数：id, x, y
     */
    self.onPointerUp = new Phaser.Signal();

    /**
     * @property {Phaser.Signal} onPointerDown - 点事件派发，派发鼠标按下，触摸开始等点事件
     * 派发参数：id, x, y
     */
    self.onPointerDown = new Phaser.Signal();

    /**
     * @property {Phaser.Signal} onPointerMove - 点事件派发，派发鼠标按下后移动，触摸移动等点事件
     * 派发参数：id, x, y
     */
    self.onPointerMove = new Phaser.Signal();

    /**
     * @property {boolean} nativeMode - 是否直接使用浏览器事件，使用时，所有事件处理处于事件堆栈，否则将事件归集到游戏堆栈处理
     * 注意：javascript 为单线程处理，底层将所有处理当做任务添加到线程中进行处理
     */
    self.nativeMode = false;

    /**
     * @property {qc.Keyboard} keyboard - 键盘监控
     */
    self.keyboard = new qc.Keyboard(this);

    /**
     * @property {qc.Mouse} mouse - 鼠标监控
     */
    self.mouse = new qc.Mouse(this);

    /**
     * @property {qc.Touch} touch - 触摸监控
     */
    self.touch = new qc.Touch(this);

    /**
     * @property {qc.BaseInputModule} module - 事件处理整理模块
     */
    self.module = new qc.BaseInputModule(self.game, self);

    // 进行初始化
    self._init();
};
Input.prototype = {};
Input.prototype.constructor = Input;

Object.defineProperties(Input.prototype, {

    /**
     * @property {number} wheelDeltaX - 本帧滚动的X轴距离
     * @readonly
     */
    wheelDeltaX: {
        get: function() { return this._wheelDeltaX; }
    },

    /**
     * @property {number} wheelDeltaY - 本帧滚动的Y轴距离
     * @readonly
     */
    wheelDeltaY: {
        get: function() { return this._wheelDeltaY; }
    },

    /**
     * @property {number} keyCount - 本帧按下的按键数量
     * @readonly
     */
    keyCount: {
        get: function() { return this.keyboard.keyCount; }
    },

    /**
     * @property {number} keyCodes - 本帧按下的按键键值
     * @readonly
     */
    keyCodes: {
        get: function() { return this.keyboard.keyCodes; }
    },

    /**
     * @property {number} mouseCount - 返回当前鼠标按下的数量
     * @readonly
     */
    mouseCount: {
        get: function() { return this.mouse.mouseCount; }
    },

    /**
     * @property {[number]} mouseIds - 返回当前所有按下的鼠标Id
     * @readonly
     */
    mouseIds: {
        get: function() { return this.mouse.mouseIds; }
    },

    /**
     * @property {number} touchCount - 返回当前触摸点的数量
     * @readonly
     */
    touchCount: {
        get: function() { return this.touch.touchCount; }
    },

    /**
     * @property {[number]} touchIds - 返回当前所有生效的触摸事件Id
     * @readonly
     */
    touchIds: {
        get: function() { return this.touch.touchIds; }
    },

    /**
     * @property {number} pointerCount - 返回当前所有点事件的数量
     * @readonly
     */
    pointerCount: {
        get: function() { return this.mouseCount + this.touchCount; }
    },

    /**
     * @property {[number]} pointers - 返回当前所有的点事件Id
     * @readonly
     */
    pointers: {
        get: function() { return this.mouseIds.concat(this.touchIds); }
    },

    /**
     * @property {boolean} enabled - 鼠标交互开关，默认启动
     */
    enable: {
        get: function() { return this._enable; },
        set: function(value) { this._setEnable(value); }
    },

    /**
     * @property {boolean} inputting - 判断当前是否处于编辑状态
     * @readonly
     */
    inputting: {
        get: function () { return this._inputting > 0}
    }    
});

/**
 * 执行初始化
 * @private
 */
Input.prototype._init = function() {
    var self = this;

    // 添加焦点失去监听
    self.phaser.game.onBlur.add(this._onBlur, this);

    // 添加键盘事件监听
    self.keyboard.onKeyUp.add(this.processKeyUp, this);
    self.keyboard.onKeyDown.add(this.processKeyDown, this);
    self.keyboard.onKeyRepeat.add(this.processKeyRepeat, this);

    // 添加鼠标事件监听
    self.mouse.onMouseDown.add(this.processPointerDown, this);
    self.mouse.onMouseUp.add(this.processPointerUp, this);
    self.mouse.onMousePressMove.add(this.processPointerMove, this);
    self.mouse.onMouseMove.add(this.processMouseMove, this);
    self.mouse.onMouseWheel.add(this.processMouseWheel, this);

    // 添加触摸事件监听
    self.touch.onTouchStart.add(this.processPointerDown, this);
    self.touch.onTouchEnd.add(this.processPointerUp, this);
    self.touch.onTouchMove.add(this.processPointerMove, this);

    self.enable = true;
};

/**
 * 销毁
 * @internal
 */
Input.prototype.destroy = function() {
    // 移除监听
    this.phaser.game.onBlur.remove(this._onBlur, this);
    this.keyboard.onKeyUp.remove(this.processKeyUp, this);
    this.keyboard.onKeyDown.remove(this.processKeyDown, this);
    this.keyboard.onKeyRepeat.remove(this.processKeyRepeat, this);

    this.mouse.onMouseDown.remove(this.processPointerDown, this);
    this.mouse.onMouseUp.remove(this.processPointerUp, this);
    this.mouse.onMousePressMove.remove(this.processPointerDown, this);
    this.mouse.onMouseMove.remove(this.processMouseMove, this);
    this.mouse.onMouseWheel.remove(this.processMouseWheel, this);

    this.touch.onTouchStart.remove(this.processPointerDown, this);
    this.touch.onTouchEnd.remove(this.processPointerUp, this);
    this.touch.onTouchMove.remove(this.processPointerMove, this);

    // 销毁模块
    if (this.module)
        this.module.destroy();

    // 销毁键盘
    if (this.keyboard)
        this.keyboard.destroy();

    // 销毁鼠标
    if (this.mouse)
        this.mouse.destroy();

    // 销毁触摸
    if (this.touch)
        this.touch.destroy();

    // 释放所有事件
    this.onKeyDown.removeAll();
    this.onKeyUp.removeAll();
    this.onKeyRepeat.removeAll();

    this.onCursorMove.removeAll();
    this.onWheel.removeAll();

    this.onPointerDown.removeAll();
    this.onPointerMove.removeAll();
    this.onPointerUp.removeAll();

    // 取消引用
    this.phaser = null;
    this.module = null;
    this.keyboard = null;
    this.mouse = null;
    this.touch = null;
};

/**
 * 设置是否可以接受事件
 * @param value
 * @private
 */
Input.prototype._setEnable = function(value) {
    // enabled状态为改变不做处理
    if (this._enable === value) {
        return;
    }

    this._enable = value;

    if (this.touch)
        this.touch.enable = value;

    if (this.mouse)
        this.mouse.enable = value;

    if (this.keyboard)
        this.keyboard.enable = value;
};

/**
 * 更新当前输入的事件
 * @internal
 */
Input.prototype.update = function() {
    // 滚动距离每帧清0
    this._wheelDeltaX = 0;
    this._wheelDeltaY = 0;
    //if (!this.nativeMode) {
    this.keyboard.update();
    this.touch.update();
    this.mouse.update();
    //}
    this.module.update();
};

/**
 * 当游戏失去焦点时，处理各个模块的清理操作
 * @private
 */
Input.prototype._onBlur = function() {
    // 模块最优先处理，因为处理时可能需要获取设备的状态
    this.module.lossFocus();

    this.keyboard.lossFocus();
    this.mouse.lossFocus();
    this.touch.lossFocus();

};

/**
 * 处理按键按下的事件通知
 * @param keyCode {number} - 按键的键值
 * @param charCode {number} - 对应字符的ASCII值
 */
Input.prototype.processKeyDown = function(keyCode, charCode) {
    this.onKeyDown.dispatch(keyCode, charCode);
};

/**
 * 处理按键弹起的事件通知
 * @param keyCode {number} - 按键的键值
 * @param charCode {number} - 对应字符的ASCII值
 */
Input.prototype.processKeyUp = function(keyCode, charCode) {
    this.onKeyUp.dispatch(keyCode, charCode);
};

/**
 * 处理按键重复的事件通知
 * @param keyCode {number} - 按键的键值
 * @param charCode {number} - 对应字符的ASCII值
 */
Input.prototype.processKeyRepeat = function(keyCode, charCode) {
    this.onKeyRepeat.dispatch(keyCode, charCode);
};

/**
 * 按键是否按下
 * @param keyCode {number} - 按键的键值
 * @return {boolean}
 */
Input.prototype.isKeyDown = function(keyCode) {
    return this.keyboard.isKeyDown(keyCode);
};

/**
 * 当前帧是否有按键按下
 * @returns {Function}
 */
Input.prototype.isAnyKeyDown = function() {
    return this.keyboard.isAnyKeyDown();
};

/**
 * 是否有按键处于按下状态
 * @returns {Function}
 */
Input.prototype.isAnyKey = function() {
    return this.keyboard.isAnyKey();
};

/**
 * 功能键alt是否按下
 * @return {boolean}
 */
Input.prototype.isAltDown = function() {
    return this.isKeyDown(qc.Keyboard.ALT);
};

/**
 * 功能键constrol是否按下
 * @return {boolean}
 */
Input.prototype.isControlDown = function() {
    return this.isKeyDown(qc.Keyboard.CONTROL);
};

/**
 * 功能键shift是否按下
 * @return {boolean}
 */
Input.prototype.isShiftDown = function() {
    return this.isKeyDown(qc.Keyboard.SHIFT);
};

/**
 * 功能键win徽标键或者mac Command键是否按下
 * @return {boolean}
 */
Input.prototype.isMetaDown = function() {
    return this.isKeyDown(qc.Keyboard.META) ||
        this.isKeyDown(qc.Keyboard.META_RWIN) ||
        this.isKeyDown(qc.Keyboard.META_RMAC);
};

/**
 * 是否存在光标
 * @return {boolean}
 */
Input.prototype.hasCursor = function() {
    return !isNaN(this.cursorPosition.x) &&
            !isNaN(this.cursorPosition.y);
};

/**
 * 判断一个设备Id是否是鼠标
 * @param id {number} - 设备Id
 * @return {boolean}
 */
Input.prototype.isMouse = function(id) {
    return id < 0 && id >= qc.Mouse.BUTTON_FORWORD;
};

/**
 * 是否有鼠标按下中
 * @return {boolean}
 */
Input.prototype.isAnyMouse = function() {
    return this.mouse.isAnyMouse();
};

/**
 * 当前帧是否有鼠标按下
 * @return {boolean}
 */
Input.prototype.isAnyMouseDown = function() {
    return this.mouse.isAnyMouseDown();
};

/**
 * 指定鼠标是否按下
 * @param id {number} - 鼠标id
 * @returns {boolean}
 */
Input.prototype.isMouseDown = function(id) {
    return this.mouse.isMouseDown(id);
};

/**
 * 是否有触摸执行中
 * @returns {boolean}
 */
Input.prototype.isAnyTouch = function() {
    return this.touch.isAnyTouch();
};

/**
 * 当前帧是否有触摸开始
 * @return {boolean}
 */
Input.prototype.isAnyTouchStart = function() {
    return this.touch.isAnyTouchStart();
};

/**
 * 指定id的触摸事件是否开始
 * @param id {number} - 设备Id
 * @returns {boolean}
 */
Input.prototype.isTouchStart = function(id) {
    return this.touch.isTouchStart(id);
};

/**
 * 是否有触摸、鼠标等点事件执行中
 * @returns {boolean}
 */
Input.prototype.isAnyPointer = function() {
    return this.touch.isAnyTouch() || this.mouse.isAnyMouse();
};

/**
 * 当前帧是否有触摸、鼠标等点事件开始
 * @return {boolean}
 */
Input.prototype.isAnyPointerStart = function() {
    return this.touch.isAnyTouchStart() || this.mouse.isAnyMouseDown();
};

/**
 * 指定id的点事件是否开始
 * @param id {number} - 设备Id
 * @returns {boolean}
 */
Input.prototype.isPointerStart = function(id) {
    return this.isMouse(id) ? this.isMouseDown(id) : this.isTouchStart(id);
};

/**
 * 通过id查找已经存在的点
 * @param id {number} - 设备Id
 * @return {qc.Pointer}
 * @private
 */
Input.prototype.getPointer = function(id) {
    if (id < 0 && id >= qc.Mouse.BUTTON_FORWORD)
        return this.mouse.getMouseById(id);
    return this.touch.getTouchById(id);
};

/**
 * 鼠标移动事件的处理
 * @param x {number} - 移动到的世界x轴坐标
 * @param y {number} - 移动到的事件y轴坐标
 */
Input.prototype.processMouseMove = function(x, y) {
    this.cursorPosition.x = x;
    this.cursorPosition.y = y;

    this.onCursorMove.dispatch(x, y);
};

/**
 * 处理鼠标的滚动事件
 * @param deltaX {number} - 在x轴上滚动的距离
 * @param deltaY {number} - 在x轴上滚动的距离
 */
Input.prototype.processMouseWheel = function(deltaX, deltaY) {
    this._wheelDeltaX = deltaX;
    this._wheelDeltaY = deltaY;

    this.onWheel.dispatch(deltaX, deltaY);
};

/**
 * 处理鼠标按下，触摸开始等事件
 * @param id {number} - 设备编号
 * @param x {number} - 事件发生时的x轴坐标
 * @param y {number} - 事件发生时的y轴坐标
 */
Input.prototype.processPointerDown = function(id, x, y) {
    this.onPointerDown.dispatch(id, x, y);
};

/**
 * 处理鼠标弹起，触摸结束等事件
 * @param id {number} - 设备编号
 * @param x {number} - 事件发生时的x轴坐标
 * @param y {number} - 事件发生时的y轴坐标
 */
Input.prototype.processPointerUp = function(id, x, y) {
    this.onPointerUp.dispatch(id, x, y);
};

/**
 * 处理鼠标按下移动，触摸移动等事件
 * @param id {number} - 设备编号
 * @param x {number} - 事件发生时的x轴坐标
 * @param y {number} - 事件发生时的y轴坐标
 */
Input.prototype.processPointerMove = function(id, x, y) {
    this.onPointerMove.dispatch(id, x, y);
};

/**
 * 是否需要忽略dom上触发的事件
 */
Input.prototype.ignoreDomEvent = function(event) {
    var self = this;
    var domNode = self.findDomNodeWithEvent(event);
    return domNode && domNode.interactive && domNode.hitArea;
};

/**
 * 按事件查找对应的dom节点
 */
Input.prototype.findDomNodeWithEvent = function(event) {
    var self = this;
    var target = event.target;
    if (!target ||
        target === self.game.world.frontDomRoot ||
        target === self.game.world.backDomRoot ||
        target === self.game.canvas.parentNode) {
        return null;
    }
    while (target && !target._qc) {
        target = target.parentNode;
    }

    if (!target) {
        return null;
    }

    return target._qc;
};
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 调试接口的支持
 *
 * @class qc.Debug
 * @constructor
 * @internal
 */
var Debug = qc.Debug = function(game) {
    var self = this;
    self._game = game;

    /**
     * @property {boolean} on - 当前是否开启debug模式
     * @default false
     */
    self.on = false;

    /**
     * @property {number} animation - 动作驱动的耗时
     */
    self.animation = 0;

    /**
     * 帧调度的耗时分布
     * @type {number}
     */
    self.total = 0;
    self.logic = 0;
    self.render = 0;
    self.preUpdate = 0;
    self.update = 0;
    self.postUpdate = 0;
};

Debug.prototype = {};
Debug.prototype.constructor = Debug;

Object.defineProperties(Debug.prototype, {
    /**
     * @property {qc.Game} game - 游戏实例的引用
     * @readonly
     */
    game : {
        get : function() { return this._game; }
    },

    /**
     * @property {boolean} on - 调试模式是否开启
     */
    on : {
        get: function()  { return this._on || false; },
        set: function(v) {
            this._on = v;
            if (v) {
                // debug模式下开启trace功能
                this.game.log.enableTrace = true;
            }
        }
    }
});

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 设备信息接口支持
 *
 * @class qc.Device
 * @constructor
 * @internal
 */
var Device = qc.Device = function(game, phaser) {
    var self = this;
    self.phaser = phaser;
    self._game = game;
    self._orientation = Device.PORTRAIT;

    /**
     * @property {qc.Signal} onOrientationChange - 切换横竖屏的事件
     */
    self.onOrientationChange = new qc.Signal();
}
Device.prototype = {};
Device.prototype.constructor = Debug;

Object.defineProperties(Device.prototype, {
    /**
     * @property {qc.Game} game - 游戏实例的引用
     * @readonly
     */
    game: {
        get: function() { return this._game; }
    },

    /**
     * @property {boolean} desktop - 是否运行在PC上
     * @readonly
     */
    desktop: {
        get: function() { return this.phaser.desktop; }
    },

    /**
     * @property {boolean} iOS - 是否运行在iOS设备上
     * @readonly
     */
    iOS: {
        get: function() { return this.phaser.iOS; }
    },

    /**
     * @property {boolean} android - 是否运行在android设备上
     * @readonly
     */
    android: {
        get: function() { return this.phaser.android; }
    },

    /**
     * @property {boolean} webGL - 是否支持webGL
     * @readonly
     */
    webGL: {
        get: function() { return this.phaser.webGL; }
    },

    /**
     * @property {boolean} vibration - 是否支持震动接口
     * @readonly
     */
    vibration: {
        get: function() { return this.phaser.vibration; }
    },

    /**
     * @property {int} browser - 当前运行的浏览器
     * @readonly
     */
    browser: {
        get: function() {
            if (this.phaser.chrome) return Device.CHROME;
            if (this.phaser.arora) return Device.ARORA;
            if (this.phaser.epiphany) return Device.EPIPHANY;
            if (this.phaser.firefox) return Device.FIREFOX;
            if (this.phaser.trident) return Device.TRIDENT;
            if (this.phaser.ie) return Device.IE;
            if (this.phaser.mobileSafari) return Device.MOBILE_SAFARI;
            if (this.phaser.midori) return Device.MIDORI;
            if (this.phaser.opera) return Device.OPERA;
            if (this.phaser.safari) return Device.SAFARI;
            if (this.phaser.silk) return Device.SILK;
            if (this.phaser.UCBrowser) return Device.UCBROWSER;

            return Device.UNKNOW;
        }
    },

    /**
     * @property {number} resolution - 设备分辨率
     * @readonly
     */
    resolution: {
        get: function() {
            return window.devicePixelRatio;
        }
    },

    /**
     * @property {boolean} fullscreen - 是否支持全屏
     * @readonly
     */
    fullscreen: {
        get: function() { return this.phaser.fullscreen; }
    },

    /**
     * @property {boolean} editor - 当前是否处于editor模式
     */
    editor: {
        get: function()  { return this._editor || false; },
        set: function(v) {
            this._editor = v;
            if (v) {
                this.game.debug.on = true;
            }
        }
    },

    /**
     * @property {number} orientation - 当前设备是横版还是竖版
     */
    orientation: {
        get: function() { return this._orientation; },
        set: function(v) {
            if (this.orientation === v) return;
            this._orientation = v;

            // 扔出事件
            this.onOrientationChange.dispatch(v);
        }
    }
});

/**
 * 几种浏览器
 */
Device.UNKNOW = -1;
Device.CHROME = 0;
Device.ARORA = 1;
Device.EPIPHANY = 2;
Device.FIREFOX = 3;
Device.IE = 4;
Device.TRIDENT = 5;
Device.MOBILE_SAFARI = 6;
Device.MIDORI = 7;
Device.OPERA = 8;
Device.SAFARI = 9;
Device.WEBAPP = 10;
Device.SILK = 11;
Device.UCBROWSER = 12;

/**
 * 横版、竖版定义
 */
Device.AUTO = 0;
Device.PORTRAIT = 1;
Device.LANDSCAPE = 2;

/**
 * 重置当前的横竖屏
 * @internal
 */
Device.prototype.resetOrientation = function() {
    if (this.game.world.width > this.game.world.height)
        this.orientation = Device.LANDSCAPE;
    else
        this.orientation = Device.PORTRAIT;
};

/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
qc.RenderTexture = PIXI.RenderTexture;

qc.RenderTexture.prototype.resize = function(width, height, updateBase)
{
    if (width === this.width && height === this.height)return;

    this.valid = (width > 0 && height > 0);

    this.width = width;
    this.height = height;
    this.frame.width = this.crop.width = width * this.resolution;
    this.frame.height = this.crop.height = height * this.resolution;

    if (updateBase)
    {
        this.baseTexture.width = this.width * this.resolution;
        this.baseTexture.height = this.height * this.resolution;
    }

    if (this.renderer.type === PIXI.WEBGL_RENDERER)
    {
        this.projection.x = this.width / 2;
        this.projection.y = -this.height / 2;
    }

    if(!this.valid)return;

    this.textureBuffer.resize(this.width * this.resolution, this.height * this.resolution);
};

qc.RenderTexture.prototype.directRenderWebGL = function(displayObject, offset, clear)
{
    if(!this.valid)return;

    var record = {
        x: this.renderer.offset.x,
        y: this.renderer.offset.y
    };

    this.renderer.offset.x = offset ? offset.x : 0;
    this.renderer.offset.y = offset ? offset.y : 0;

    // time for the webGL fun stuff!
    var gl = this.renderer.gl;

    if (gl.isContextLost()) {
        return false;
    }

    if (!gl.replace) {
        var oldViewPort = gl.viewport;
        gl.viewport = function(x, y, width, height) {
            if (width === 0 || height === 0 || width * height < 0) {
                console.log('viewPort:', x, y, width, height, width * height);
            }
            oldViewPort.call(gl, x, y, width, height);
        };
        gl.replace = true;
    }
    

    gl.viewport(0, 0, this.width * this.resolution, this.height * this.resolution);

    gl.bindFramebuffer(gl.FRAMEBUFFER, this.textureBuffer.frameBuffer );

    if(clear)this.textureBuffer.clear();

    var v = gl.getError();
    if (v) {
        console.log('webgl gl.getError(', v, ')');
    }

    this.renderer.spriteBatch.dirty = true;

    this.renderer.renderDisplayObject(displayObject, this.projection, this.textureBuffer.frameBuffer);

    this.renderer.spriteBatch.dirty = true;
    this.renderer.offset.x = record.x;
    this.renderer.offset.y = record.y;
};

PIXI.RenderTexture.prototype.directRenderCanvas = function(displayObject, offset, clear)
{
    if(!this.valid)return;

    var record = {
        x: this.renderer.offset.x,
        y: this.renderer.offset.y
    };

    this.renderer.offset.x = offset ? offset.x : 0;
    this.renderer.offset.y = offset ? offset.y : 0;

    if(clear)this.textureBuffer.clear();

    var context = this.textureBuffer.context;

    var realResolution = this.renderer.resolution;

    this.renderer.resolution = this.resolution;

    this.renderer.renderDisplayObject(displayObject, context);

    this.renderer.resolution = realResolution;
    this.renderer.offset.x = record.x;
    this.renderer.offset.y = record.y;
};

qc.RenderTexture.prototype.renderWebGL = function(displayObject, matrix, clear)
{
    if(!this.valid)return;
    //TOOD replace position with matrix..

    //Lets create a nice matrix to apply to our display object. Frame buffers come in upside down so we need to flip the matrix
    var wt = displayObject.getWorldTransform();
    wt.identity();
    wt.translate(0, this.projection.y * 2);
    if(matrix)wt.append(matrix);
    wt.scale(1,-1);
    // setWorld Alpha to ensure that the object is renderer at full opacity
    displayObject.worldAlpha = 1;
    displayObject._isSubNeedCalcTransform = true;
    // Time to update all the children of the displayObject with the new matrix..
    var children = displayObject.children;

    for(var i=0,j=children.length; i<j; i++)
    {
        children[i].updateTransform();
    }
    // time for the webGL fun stuff!
    var gl = this.renderer.gl;

    gl.viewport(0, 0, this.width * this.resolution, this.height * this.resolution);

    gl.bindFramebuffer(gl.FRAMEBUFFER, this.textureBuffer.frameBuffer );

    if(clear)this.textureBuffer.clear();

    this.renderer.spriteBatch.dirty = true;

    this.renderer.renderDisplayObject(displayObject, this.projection, this.textureBuffer.frameBuffer);

    this.renderer.spriteBatch.dirty = true;
    displayObject._isNotNeedCalcTransform = false;
};

/**
 * This function will draw the display object to the texture.
 *
 * @method renderCanvas
 * @param displayObject {DisplayObject} The display object to render this texture on
 * @param [matrix] {Matrix} Optional matrix to apply to the display object before rendering.
 * @param [clear] {Boolean} If true the texture will be cleared before the displayObject is drawn
 * @private
 */
PIXI.RenderTexture.prototype.renderCanvas = function(displayObject, matrix, clear)
{
    if(!this.valid)return;

    var wt = displayObject.worldTransform;
    wt.identity();
    if(matrix)wt.append(matrix);

    // setWorld Alpha to ensure that the object is renderer at full opacity
    displayObject.worldAlpha = 1;
    displayObject._isSubNeedCalcTransform = true;
    // Time to update all the children of the displayObject with the new matrix..
    var children = displayObject.children;

    for(var i = 0, j = children.length; i < j; i++)
    {
        children[i].updateTransform();
    }

    if(clear)this.textureBuffer.clear();

    var context = this.textureBuffer.context;

    var realResolution = this.renderer.resolution;

    this.renderer.resolution = this.resolution;

    this.renderer.renderDisplayObject(displayObject, context);

    this.renderer.resolution = realResolution;
    displayObject._isNotNeedCalcTransform = false;
};
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 软件裁切的管理，使用多边形裁切算法，对节点进行裁切
 * @type {Function}
 */
var SoftClipManager = qc.SoftClipManager = function(renderSession) {
    /**
     * @property {WebGLRenderSession} renderSession - 绘制上下文
     */
    this.renderSession = renderSession;

    /**
     * @property {[]} _polygons - 当前裁切的多边形
     * @private
     */
    this._polygons = [];
};
SoftClipManager.prototype = {};
SoftClipManager.prototype.constructor = SoftClipManager;

Object.defineProperties(SoftClipManager.prototype, {
    /**
     * @property {boolean} needClip - 是否需要裁切
     */
    needClip : {
        get : function() {
            return !!this._polygons.length;
        }
    }
});

/**
 * 获取裁切管理器
 * @param renderSession
 * @returns {SoftClipManager|*|softClipManager}
 */
SoftClipManager.getManager = function(renderSession) {
    var clipMgr = renderSession.softClipManager;
    if (!clipMgr) {
        clipMgr = renderSession.softClipManager = new SoftClipManager(renderSession);
    }
    return clipMgr;
};

/**
 * 添加一个裁切多边形
 * @param polygon
 */
SoftClipManager.prototype.pushPolygon = function(polygon) {
    var self = this;
    var polygons = [];
    self.renderSession.spriteBatch.batchType = qc.BATCH_TRIANGLES;
    if (!self._polygons.length) {
        var len = arguments.length;
        while (len--) {
            var one = arguments[len];
            var ret = qc.GeometricTool.quickDecomp(one);
            if (ret.length)
                Array.prototype.push.apply(polygons, ret);
            else {
                var tri = GeometricTool.Triangulate(one);
                for (var idx = 0; idx < tri.length; idx += 3) {
                    var first = tri[idx];
                    var second = tri[idx + 1];
                    var third = tri[idx + 2];

                    polygons.push([
                        one[first],
                        one[second],
                        one[third]
                    ]);
                }
            }
        }
        self._polygons.push(polygons);
        return;
    }
    var last = self._polygons[self._polygons.length - 1];
    var lastLen = last.length;
    while (lastLen--) {
        var mask = last[lastLen];
        var len = arguments.length;
        while (len--) {
            polygon = arguments[len];
            var ret = qc.GeometricTool.sutherlandHodgman(polygon, mask);
            polygons.push(ret);
        }
    }
    self._polygons.push(polygons);
};

/**
 * 移除最后一个裁切多边形
 * @param polygon
 */
SoftClipManager.prototype.popPolygon = function() {
    var self = this;
    if (self._polygons.length) {
        self._polygons.splice(self._polygons.length - 1, 1);
    }
    if (!self._polygons.length) {
        self.renderSession.spriteBatch.batchType = qc.BATCH_QUAD;
    }
};

/**
 * 得到当前变换矩阵下的裁切多边形
 * @param a
 * @param b
 * @param c
 * @param d
 * @param tx
 * @param ty
 */
SoftClipManager.prototype.getClipPolygon = function(a, b, c, d, tx, ty) {
    var self = this;
    var masks = [];
    var last = self._polygons[self._polygons.length - 1];
    var lastLen = last.length;

    var id = 1 / (a * d + c * -b);
    a *= id;
    b *= id;
    c *= id;
    d *= id;
    var offX = ty * c - tx * d;
    var offY = -ty * a + tx * b;
    var index = -1;
    while (++index < lastLen) {
        var polygon = last[index];
        var mask = [];
        var pLen = polygon.length;
        var pIndex = -1;
        while (++pIndex < pLen) {
            var point = polygon[pIndex];
            mask.push(new qc.Point(
                d * point.x - c * point.y + offX,
                a * point.y - b * point.x + offY));
        }
        masks.push(mask);
    }
    return masks;
};

/**
 * 绘制一个 sprite
 * @param renderSession
 * @param sprite
 * @param w1
 * @param h1
 * @param w0
 * @param h0
 * @param uvx0
 * @param uvy0
 * @param uvx1
 * @param uvy1
 * @param a
 * @param b
 * @param c
 * @param d
 * @param tx
 * @param ty
 * @param tint
 */
SoftClipManager.prototype.renderSprite = function(renderSession, sprite,
                                                  w1, h1, w0, h0,
                                                  uvx0, uvy0, uvx1, uvy1,
                                                  a, b, c, d, tx, ty,
                                                  tint) {
    var clips = this.getClipPolygon(a, b, c, d, tx, ty);
    if (clips.length === 0)
        return;
    var corner = [
        new qc.Point(w1, h1),
        new qc.Point(w0, h1),
        new qc.Point(w0, h0),
        new qc.Point(w1, h0)
    ];
    var rect = { x : w1, y : h1, width : w0 - w1, height : h0 - h1 };
    if (!rect.width || !rect.height) {
        return;
    }
    var calcUV = function(point, uvx0, uvx1, uvy0, uvy1, rect) {
        return {
            x : uvx0 + (point.x - rect.x) / rect.width * (uvx1 - uvx0),
            y : uvy0 + (point.y - rect.y) / rect.height * (uvy1 - uvy0)
        }
    };

    // 对每个裁切多边形进行裁切
    var clipLen = clips.length;
    while (clipLen--) {
        var clip = clips[clipLen];
        var result = qc.GeometricTool.sutherlandHodgman(clip, corner);
        var retLen = result.length;
        if (retLen < 3)
            continue;
        var uvs = [];
        var worldPos = [];
        var idx = -1;
        while (++idx < retLen) {
            uvs[idx] = calcUV(result[idx], uvx0, uvx1, uvy0, uvy1, rect);
            worldPos.push({
                x : a * result[idx].x + c * result[idx].y + tx,
                y : d * result[idx].y + b * result[idx].x + ty

            });
        }
        var tmp = qc.GeometricTool.Triangulate(worldPos);
        if (!tmp)
            continue;
        for (var idx = 0; idx < tmp.length; idx += 3) {
            var first = tmp[idx];
            var second = tmp[idx + 1];
            var third = tmp[idx + 2];

            this._addWebGLTriangle(renderSession.spriteBatch, sprite, tint,
                worldPos[first], worldPos[second], worldPos[third],
                uvs[first], uvs[second], uvs[third]
            )
        }
    }
};

/**
 * 添加一个三角形
 * @param spriteBatch
 * @param sprite
 * @param tint
 * @param p1
 * @param p2
 * @param p3
 * @param uv1
 * @param uv2
 * @param uv3
 * @private
 */
SoftClipManager.prototype._addWebGLTriangle = function(spriteBatch, sprite, tint, p1, p2, p3, uv1, uv2, uv3) {
    if(spriteBatch.currentBatchSize >= spriteBatch.triangleSize)
    {
        spriteBatch.flush();
        spriteBatch.currentBaseTexture = sprite.texture.baseTexture;
    }

    var colors = spriteBatch.colors;
    var positions = spriteBatch.positions;
    var index = spriteBatch.currentBatchSize * 3 * spriteBatch.vertSize;


    if(spriteBatch.renderSession.roundPixels)
    {
        // xy
        positions[index] = p1.x | 0;
        positions[index+1] = p1.y | 0;

        // xy
        positions[index+5] = p2.x | 0;
        positions[index+6] = p2.y | 0;

        // xy
        positions[index+10] = p3.x | 0;
        positions[index+11] = p3.y | 0;
    }
    else
    {
        // xy
        positions[index] = p1.x;
        positions[index+1] = p1.y;

        // xy
        positions[index+5] = p2.x;
        positions[index+6] = p2.y;

        // xy
        positions[index+10] = p3.x;
        positions[index+11] = p3.y;
    }

    // uv
    positions[index+2] = uv1.x;
    positions[index+3] = uv1.y;

    // uv
    positions[index+7] = uv2.x;
    positions[index+8] = uv2.y;

    // uv
    positions[index+12] = uv3.x;
    positions[index+13] = uv3.y;

    // color and alpha
    colors[index+4] = colors[index+9] = colors[index+14] = tint;

    // increment the batchsize
    spriteBatch.sprites[spriteBatch.currentBatchSize++] = sprite;

};
/**
 * @author luohj
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 声音管理器
 *
 * @class qc.SoundManager
 * @param {qc.Game} game - A reference to the currently running game.
 * @constructor
 * @internal
 */
var SoundManager = qc.SoundManager = function(game) {
    this.game = game;
    this.phaser = game.phaser.sound;
    this.phaser._qc = this;

    var self = this;
    game.onStart.add(function() {
        self._boot();
    });
};
SoundManager.prototype.constructor = SoundManager;

// 初始化声音处理
SoundManager.prototype._boot = function() {
    var phaserSound = this.phaser;
    var input = this.game.input;

    if (!phaserSound.game.device.cocoonJS && phaserSound.game.device.iOS || (window['PhaserGlobal'] && window['PhaserGlobal'].fakeiOSTouchLock))
    {
        input.touch.callbackContext = phaserSound;

        // iOS9下必须在touchEnd进行unlock
        // https://github.com/photonstorm/phaser/commit/f64fc42f3e28c8f02562234ad8d09fd9d49fd24a
        if (phaserSound.game.device.iOSVersion > 8) {
            input.touch.touchEndCallback = phaserSound.unlock;
        }
        else {
            input.touch.touchStartCallback = phaserSound.unlock;
        }

        input.mouse.callbackContext = phaserSound;
        input.mouse.mouseDownCallback = phaserSound.unlock;
        phaserSound.touchLocked = true;
    }
    else
    {
        phaserSound.touchLocked = false;
    }
};

Object.defineProperties(SoundManager.prototype, {
    /**
     * 静音设置：ture 为 静音 false为非静音
     * @property {boolean} mute
     */
    'mute' : {
        get : function() { return this.phaser.mute; },
        set : function(v) { this.phaser.mute = v; }
    },

    /**
     * 音量设置
     * @property {number} volume
     */
    'volume' : {
        get : function() { return this.phaser.volume; },
        set : function(v) {
            this.phaser.volume = v;
        }
    },

    /**
     * 声音模式 -- 使用 web audio
     */
    'usingWebAudio' : {
        get : function() { return this.phaser.usingWebAudio; }
    },

    /**
     * 声音模式 -- 使用 audio tag
     */
    'usingAudioTag' : {
        get : function() { return this.phaser.usingAudioTag; }
    },

    /**
     * 是否支持mp3播放
     */
    mp3Support: {
        get: function() {
            return this.game.phaser.device.mp3;
        }
    },

    /**
     * 是否支持ogg播放
     */
    oggSupport: {
        get: function() {
            return (this.game.phaser.device.ogg || this.game.phaser.device.opus);
        }
    },

    /**
     * 是否支持web audio播放
     */
    webAudioSupport: {
        get: function() {
            return this.game.phaser.sound.usingWebAudio;
        }
    }
});

/**
 * 获取声音文件对应的浏览器支持格式的 url
 */
SoundManager.prototype.tryGetUrl = function(url) {
    var newUrl = url;

    if (newUrl.indexOf('.mp3.bin') > 0) {
        if (!this.mp3Support) {
            newUrl = newUrl.replace('.mp3.bin', '.ogg.bin');
        }
    }
    else if (newUrl.indexOf('.ogg.bin') > 0) {
        if (!this.oggSupport) {
            newUrl = newUrl.replace('.ogg.bin', '.mp3.bin');
        }
    }

    if (!this.webAudioSupport) {
        newUrl = newUrl.replace('.bin', '');
    }

    // 返回结果
    return newUrl;
};
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 键盘事件
 * @class {qc.Key}
 * @constructor
 * @param {qc.Game} game - 游戏
 * @param {number} keycode - 按键的keycode
 */
var Key = qc.Key = function(game, keycode) {
    /**
     * @property {qc.Game} game - 记录当前Game对象
     */
    this.game = game;

    /**
     * @property {number} _keycode - 记录点事件的id
     */
    this._keycode = keycode;

    /**
     * @property {boolean} _isDown - 是否按下
     * @private
     */
    this._isDown = false;

    /**
     * @property {boolean} _justDown - 是否刚按下还未派发down事件
     * @private
     */
    this._justDown = false;

    /**
     * @property {boolean} _justUp - 是否刚弹起还未派发up事件
     * @private
     */
    this._justUp = false;

    /**
     * @property {number} downTime - 按键按下的时间
     */
    this.downTime = 0;

    /**
     * @property {number} repeats - 当前按键重复的次数
     */
    this.repeats = 0;

    /**
     * @property {number} _lastRepeatTime - 最后一次重复事件的时间
     * @private
     */
    this._lastRepeatTime = 0;

    /**
     * @property {Array} _eventList - 当前还未处理的事件列表
     * @private
     */
    this._eventList = [];
};
Key.prototype = {};
Key.prototype.constructor = Key;

Object.defineProperties(Key.prototype, {
    /**
     * @property {number} keyCode - 按键事件的keyCode
     * @readonly
     */
    keyCode: {
        get: function() { return this._keycode; }
    },

    /**
     * @property {number} currCharCode - 按键事件当前对应的字符的ASCII码
     */
    currCharCode: {
        get: function() { return this._charCode; },
        set: function(value) {
            this._charCode = value;
        }
    },

    /**
     * @property {boolean} isJustDown - 是否刚刚按下
     * @readonly
     */
    isJustDown: {
        get: function() {
            var value = this._justDown;
            if (value) {
                this._justDown = false;
                this._isDown = true;
            }
            return value;
        }
    },

    /**
     * @property {boolean} isJustUp - 是否刚刚弹起
     * @readonly
     */
    isJustUp: {
        get: function() {
            var value = this._justUp;
            if (value) {
                this._justUp = false;
                this._isDown = false;
            }
            return value;
        }
    },

    /**
     * @property {boolean} isDown - 按键是否按下
     * @readonly
     */
    isDown: {
        get: function() { return this._isDown; }
    },

    /**
     * @prototype {number} duringTime - 开始经历的时间,负值表示还未按下
     * @readonly
     */
    duringTime: {
        get: function() {
            if (!this.isDown) {
                return -1;
            }
            return this.game.time.fixedTime - this.downTime;
        }
    }
});

/**
 * 销毁
 * @internal
 */
Key.prototype.destroy = function() {
    this.game = null;
};

/**
 * 是否还有事件没有处理
 * @returns {boolean}
 */
Key.prototype.hasEvent = function() {
    return !!this._eventList.length;
};

/**
 * 更新下一个事件
 * @return {boolean} 返回是否还有事件未处理
 * @internal
 */
Key.prototype.nextEvent = function() {
    if (!this._eventList.length) {
        return false;
    }
    var event = this._eventList.shift();
    do {
        if (!event)
            break;
        switch(event.type) {
            case Key.DOWN:
                this._onDown(event.time);
                break;
            case Pointer.UP:
                this._onUp();
                break;
        }
        return true;
    } while(false);
    return false;
};

/**
 * 按键按下
 * @return {boolean} 状态是否有变更
 * @internal
 */
Key.prototype.onDown = function() {
    // 记录事件
    this._eventList.push({
        type: Key.DOWN,
        time: this.game.time.fixedTime
    });
    return true;
};

/**
 * 处理按键弹起
 * @return {boolean} 状态是否有变更
 * @internal
 */
Key.prototype.onUp = function() {
    // 记录事件
    this._eventList.push({
        type: Key.UP
    });
    return true;
};

/**
 * 处理按键按下
 * @return {boolean} 状态是否有变更
 * @internal
 */
Key.prototype._onDown = function(downTime) {
    if (this.isDown || this._justDown)
        return false;
    this._justDown = true;
    this.downTime = downTime;
    this._lastRepeatTime = this.downTime;
    return true;
};

/**
 * 按键弹起
 * @return {boolean} 状态是否有变更
 * @internal
 */
Key.prototype._onUp = function() {
    if (!(this._isDown || this._justDown) || this._justUp) {
        return false;
    }

    this._justUp = true;
    return true;
};

/**
 * 直接设置状态
 * @param isDown {boolean} 是否是按下状态
 * @return {boolean} 状态是否有改变
 */
Key.prototype.setState = function(isDown) {
    var oldState = (this._isDown || this._justDown) && !this._justUp;
    this._isDown = isDown;
    this._justDown = false;
    this._justUp = false;
    return oldState ^ isDown;
};

/**
 * 返回是否触发重复按键
 * @param repeatInterval {nubmer} - 检测的间隔时间
 * @returns {boolean}
 */
Key.prototype.checkRepeat = function(repeatInterval) {
    if (!this.isDown)
        return false;
    var time = this.game.time.fixedTime;
    if (time - this._lastRepeatTime - repeatInterval > 0) {
        this.repeats++;
        this._lastRepeatTime = time;
        return true;
    }
    return false;
};

Key.UP = -1;
Key.DOWN = 0;
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 鼠标、触摸等产生点事件
 * @class {qc.Pointer}
 * @constructor
 * @param {qc.Game} game - 游戏
 * @param {number} id - 按键的编号
 */
var Pointer = qc.Pointer = function(game, id) {

    /**
     * @property {qc.Game} game - 记录当前Game对象
     */
    this.game = game;

    /**
     * @property {number} _id - 记录点事件的id, 内部ID，鼠标从-5到-1，触摸从1开始
     * @private
     */
    this._id = id;

    /**
     * @property {boolean} _isDown - 是否按下
     * @private
     */
    this._isDown = false;

    /**
     * @property {boolean} _justDown - 是否刚按下还未派发down事件
     * @private
     */
    this._justDown = false;

    /**
     * @property {boolean} _justUp - 是否刚弹起还未派发up事件
     * @private
     */
    this._justUp = false;

    /**
     * @property {number} downTime - 按键按下的时间
     */
    this.downTime = 0;

    /**
     * @property {boolean} withinGame - 是否在游戏区域内
     * @type {Boolean}
     */
    this.withinGame = false;

    /**
     * @property {qc.Point | null} previousDownPoint - 上一次点击的状态
     */
    this.previousStatus = [];

    /**
     * @property {Array} _eventList - 当前还未处理的事件列表
     * @private
     */
    this._eventList = [];
};
Pointer.prototype = {};
Pointer.prototype.constructor = Pointer;

Object.defineProperties(Pointer.prototype, {
    /**
     * @property {number} id - 点事件的id
     * @readonly
     */
    id: {
        get: function() { return this._id; }
    },

    /**
     * @property {number} x - 点事件在world坐标系中的X轴坐标
     * @readonly
     */
    x: {
        get: function() { return this._x; }
    },

    /**
     * @property {number} y - 点事件在world坐标系中的Y轴坐标
     * @readonly
     */
    y: {
        get: function() { return this._y; }
    },

    /**
     * @property {number} startX - 记录事件开始时在world坐标系中的X轴坐标
     * @readonly
     */
    startX: {
        get: function() { return this._startX; }
    },

    /**
     * @property {number} startY - 记录事件开始时在world坐标系中的Y轴坐标
     * @readonly
     */
    startY: {
        get : function() { return this._startY; }
    },

    /**
     * @property {boolean} isMouse - 是否是鼠标产生的点事件
     * @readonly
     */
    isMouse: {
        get: function() {
            return this._id < qc.Mouse.NONE && this._id >= qc.Mouse.BUTTON_FORWORD;
        }
    },

    /**
     * @property {boolean} isJustDown - 是否刚刚按下
     * @readonly
     */
    isJustDown: {
        get: function() {
            var value = this._justDown;
            if (value) {
                this._justDown = false;
                this._isDown = true;
            }
            return value;
        }
    },

    /**
     * @property {boolean} isJustUp - 是否刚刚弹起
     * @readonly
     */
    isJustUp: {
        get: function() {
            var value = this._justUp;
            if (value) {
                this._justUp = false;
                this._isDown = false;
            }
            return value;
        }
    },

    /**
     * @property {boolean} isDown - 按键是否按下
     * @readonly
     */
    isDown: {
        get: function() { return this._isDown; }
    },

    /**
     * @property {boolean} isDowning - 是否按下过程中
     * @readonly
     */
    isDowning: {
        get: function() { return this._justDown; }
    },

    /**
     * @property {boolean} isUpping -  是否弹起过程中
     * @readonly
     */
    isUpping: {
        get: function() { return this._justUp; }
    },

    /**
     * @property {number} deltaX - 按键最后距离上次处理在x轴上移动的距离
     * @readonly
     */
    deltaX: {
        get: function() { return this._deltaX; }
    },

    /**
     * @property {number} deltaY - 按键最后距离上次处理在y轴上移动的距离
     * @readonly
     */
    deltaY: {
        get: function() { return this._deltaY; }
    },

    /**
     * @property {number} distanceX - 本次事件期间在x轴上移动的距离
     * @readonly
     */
    distanceX: {
        get: function() { return this.x - this.startX; }
    },

    /**
     * @property {number} distanceY - 本次事件期间在y轴上移动的距离
     * @readonly
     */
    distanceY: {
        get: function() { return this.y - this.startY; }
    },

    /**
     * @property {number} deviceId - 按键在系统中的id
     */
    deviceId: {
        get: function() { return this._deviceId || this.id; },
        set: function(value) { this._deviceId = value; }
    },

    /**
     *  @property {number} eventId - 事件的Id，默认返回设备Id
     */
    eventId: {
        get: function() { return this._eventId || this.id; },
        set: function(value) { this._eventId = value; }
    },

    /**
     * @prototype {number} duringTime - 开始经历的时间,负值表示还未按下
     * @readonly
     */
    duringTime: {
        get: function() {
            if (!this.isDown) {
                return -1;
            }
            return this.game.time.fixedTime - this.downTime;
        }
    }
});

/**
 * 销毁
 * @internal
 */
Pointer.prototype.destroy = function() {
    this.game = null;
};

/**
 * 更新
 * @return {[type]} [description]
 */
Pointer.prototype.update = function() {
    this._deltaX = this._tempX - this._x;
    this._deltaY = this._tempY - this._y;
    this._x = this._tempX;
    this._y = this._tempY;
};

/**
 * 是否还有事件没有处理
 * @returns {boolean}
 */
Pointer.prototype.hasEvent = function() {
    return !!this._eventList.length;
};

/**
 * 更新下一个事件
 * @return {boolean} 返回是否还有事件未处理
 * @internal
 */
Pointer.prototype.nextEvent = function() {
    if (!this._eventList.length) {
        return false;
    }
    var event = this._eventList.shift();
    do {
        if (!event) 
            break;
        switch(event.type) {
            case Pointer.DOWN:
                this.withinGame = true;
                this._onDown(event.x, event.y, event.time);
                break;
            case Pointer.UP:
                this._onUp(event.time);
                break;
            case Pointer.LEAVE:
                this.withinGame = false;
                this._onMove(event.x, event.y);
                break;
            case Pointer.ENTER:
                this.withinGame = true;
                this._onMove(event.x, event.y);
                break;
            case Pointer.MOVE:
                this._onMove(event.x, event.y);
                break;
            case Pointer.CANCEL:
                this._onCancel(event.time);
                break;
        }
        return true;
    } while(false);
    return false;
};

/**
 * 事件开始
 * @param  {[type]} x [description]
 * @param  {[type]} y [description]
 * @return {[type]}   [description]
 */
Pointer.prototype.onDown = function(x, y) {
    // 记录事件
    this._eventList.push({
        type: Pointer.DOWN,
        x: x,
        y: y,
        time: this.game.time.fixedTime
    });
    return true;
};

/**
 * 事件移动
 * @param  {[type]} x [description]
 * @param  {[type]} y [description]
 * @return {[type]}   [description]
 */
Pointer.prototype.onMove = function(x, y) {
    // 记录事件
    var exist = this._eventList.length;
    if (exist > 0 && this._eventList[exist - 1].type === Pointer.MOVE) {
        this._eventList[exist - 1].x = x;
        this._eventList[exist - 1].y = y;
    }
    else {
        this._eventList.push({
            type: Pointer.MOVE,
            x: x,
            y: y
        });    
    }
    return true;
};

/**
 * 事件离开游戏区域
 * @param  {[type]} x [description]
 * @param  {[type]} y [description]
 * @return {[type]}   [description]
 */
Pointer.prototype.onLeave = function(x, y) {
    // 记录事件
    this._eventList.push({
        type: Pointer.LEAVE,
        x: x,
        y: y
    });
    return true;
};

/**
 * 事件进入游戏区域
 * @param  {[type]} x [description]
 * @param  {[type]} y [description]
 * @return {[type]}   [description]
 */
Pointer.prototype.onEnter = function(x, y) {
    // 记录事件
    this._eventList.push({
        type: Pointer.ENTER,
        x: x,
        y: y
    });
    return true;
};

/**
 * 事件弹起
 * @return {[type]} [description]
 */
Pointer.prototype.onUp = function() {
    // 记录事件
    this._eventList.push({
        type: Pointer.UP,
        time: this.game.time.fixedTime
    });
    return true;
};

/**
 * 事件取消
 * @return {[type]} [description]
 */
Pointer.prototype.onCancel = function() {
    // 记录事件
    this._eventList.push({
        type: Pointer.CANCEL,
        time: this.game.time.fixedTime
    });
    return true;
};

/**
 * 按键按下
 * @param x {number} - 按键按下时的x坐标
 * @param y {number} - 按键按下时的y坐标
 * @param downTime {number} - 按键按下时的时间
 * @return {boolean} 状态是否有变更
 * @internal
 */
Pointer.prototype._onDown = function(x, y, downTime) {
    if (this.isDown || this._justDown)
        return false;
    this._justDown = true;
    this.downTime = downTime;
    this._x = x;
    this._y = y;
    this._startX = x;
    this._startY = y;
    this._tempX = x;
    this._tempY = y;
    this._deltaX = 0;
    this._deltaY = 0;
    return true;
};

/**
 * 按键弹起
 * @return {boolean} 状态是否有变更
 * @internal
 */
Pointer.prototype._onUp = function(upTime) {
    if (!(this._isDown || this._justDown) || this._justUp)
        return false;
    this._justUp = true;

    // 记录上一次点击的状态，用于特殊判断
    this.previousStatus.unshift({
        startX: this._startX,
        startY: this._startY,
        endX: this.x,
        endY: this.y,
        downTime: this.downTime,
        upTime: upTime
    });
    while (this.previousStatus.length > 10) {
        this.previousStatus.pop();
    }
    return true;
};

/**
 * 取消按键
 * @return {boolean} 状态是否变更
 */
Pointer.prototype._onCancel = function(cancelTime) {
    if (this._justUp)
        return false;
    this._onUp(cancelTime);
    return true;
};


/**
 * 更新当前按键的位置
 * @param x {number} - 按键当前的x坐标
 * @param y {number} - 按键当前的y坐标
 */
Pointer.prototype._onMove = function(x, y) {
    this._tempX = x;
    this._tempY = y;
};

/**
 * 是否是双击
 * @param doubleTime {number} - 双击允许的时间间隔
 * @param distance {number} - 双击允许的距离间隔
 */
Pointer.prototype.isDouble = function(doubleTime, distance) {

};

Pointer.UP = -1;
Pointer.DOWN = 0;
Pointer.MOVE = 1;
Pointer.ENTER = 2;
Pointer.LEAVE = 3;
Pointer.CANCEL = 4;
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 键盘输入交互功能。
 *
 * @class qc.Keyboard
 * @param {qc.Input} input - 输入事件处理器
 * @param {DOM} generator -  事件的发生器，一般为DOM元素，默认为window
 * @internal
 */
var Keyboard = qc.Keyboard = function(input, generator) {

    /**
     * @property {qc.Input} input -  本地的交互事件处理器
     */
    this.input = input;

    /**
     * @property {qc.Game} game - 游戏对象
     */
    this.game = input.game;

    /**
     * @property {number} repeatInterval - 按键重复的间隔时间，单位毫秒(ms)
     */
    this.repeatInterval = 100;

    /**
     * @property {Phaser.Signal} onKeyDown - 按键按下时的事件
     * 通知参数：keyCode, charCode
     */
    this.onKeyDown = new Phaser.Signal();

    /**
     * @property {Phaser.Signal} onKeyUp - 按键弹起时的事件
     * 通知参数：keyCode, charCode
     */
    this.onKeyUp = new Phaser.Signal();

    /**
     * @property {Phaser.Signal} onKeyRepeat - 按键重复时的事件
     * 通知参数：keyCode, charCode
     */
    this.onKeyRepeat = new Phaser.Signal();

    /**
     * @property [qc.Key] _keys - 缓存当前按下的key
     * @private
     */
    this._keys = [];

    /**
     * @property {{number: boolean}} _keyCodes - 缓存当前按下的keyCode
     * @private
     */
    this._keyCodes = {};

    /**
     * @property {[qc.Key]} _capture - 按键事件是否被中止派发
     * @private
     */
    this._capture = {};

    /**
     * @property {number} _keyDownNum - 当前按下的按键数
     * @private
     */
    this._keyDownNum = 0;

    /**
     * @property {boolean} _anyKeyDown - 当前帧内是否有按键按下
     * @private
     */
    this._anyKeyDown = false;

    // 进行初始化
    this._init(generator);
};
Keyboard.prototype = {};
Keyboard.prototype.constructor = Keyboard;

Object.defineProperties(Keyboard.prototype, {

    /**
     * @property {boolean} enabled - 键盘交互开关，初始化时默认启动
     */
    enable: {
        get: function() { return this._enable; },
        set: function(value) { this._setEnable(value); }
    },

    /**
     *
     * @property {DOM} generator - 键盘事件的发生器，一般为HTML DOM对象
     * 当设置为空时，使用window作为事件发生器
     */
    generator: {
        get: function() { return this._generator || window; },
        set: function(value) {
            // 监听对象没有修改，不进行操作
            if (value === this._generator)
                return;

            var oldState = this.enable;

            // 先注销掉之前的监听
            this.enable = false;

            // 替换发生器
            this._generator = value;

            if (oldState) {
                // 设置事件可用
                this.enable = true;
            }

        }
    },

    /**
     * @property {number} gainFocusType - 键盘事件发生器获取焦点的类型，当发生器不是window时生效
     */
    gainFocusType: {
        get: function() { return this._gainFocusType; },
        set: function(value) { this._gainFocusType = value; }
    },

    /**
     * @property {number} keyCount - 返回当前按下的按键数量
     * @readonly
     */
    keyCount: {
        get: function() { return this._keyDownNum; }
    },

    /**
     * @property {[number]} keyCodes - 返回当前所有按下的键值
     * @readonly
     */
    keyCodes: {
        get: function() { return Object.keys(this._keyCodes); }
    }
});

/**
 * 初始化
 * @param generator
 * @private
 */
Keyboard.prototype._init = function(generator) {
    // 键盘交互事件回调
    this._onKeyDown = this._processKeyDown.bind(this);
    this._onKeyUp = this._processKeyUp.bind(this);
    this._onBlur = this.lossFocus.bind(this);

    // 焦点获取涉及到的鼠标触摸事件
    this._onMouseOver = this._onOver.bind(this);
    this._onMouseClick = this._onClick.bind(this);

    // 设置事件发生器
    this.generator = generator;
    this.gainFocusType = Keyboard.GAINFOCUS_CLICK;
    this.enable = true;

    // window PC 的 alt down 会使游戏失去焦点，导致后续的按键事件无法触发
    // 此处将 alt 事件捕获
    if (this.game.device.phaser.windows && this.game.device.phaser.desktop)
        this.addKeyCapture(Keyboard.ALT);
};

/**
 * 销毁
 * @internal
 */
Keyboard.prototype.destroy = function() {
    this.enable = false;

    this.input = null;
    this.generator = null;

    this._onKeyDown = null;
    this._onKeyUp = null;
    this._onBlur = null;
    this._onMouseOver = null;
    this._onMouseClick = null;
};

/**
 * 更新当前输入的事件
 * @internal
 */
Keyboard.prototype.update = function() {
    if (!this.enable)
        return;

    this._anyKeyDown = false;

    var i = this._keys.length;
    var key;
    while (i--) {
        key = this._keys[i];
        if (!key)
            continue;
        while (key.nextEvent()) {
            // 先检测按下
            if (key.isJustDown) {
                this._anyKeyDown = true;
                this._recordKey(key.keyCode, true);
                this.onKeyDown.dispatch(key.keyCode);
            }

            // 检测弹起
            if (key.isJustUp) {
                this._recordKey(key.keyCode, false);
                this.onKeyUp.dispatch(key.keyCode);
            }

            // 检测重复事件
            if (key.checkRepeat(this.repeatInterval))
                this.onKeyRepeat.dispatch(key._keycode);
        }
    }
};

/**
 * 判定键值是否按下
 * @param keyCode 指定键值
 * @return {boolean} 如果按下返回true，否则返回false
 */
Keyboard.prototype.isKeyDown = function(keyCode) {
    return this._keys[keyCode] && this._keys[keyCode].isDown;
};

/**
 * 判定是否有任何按键处于按下状态
 * @return {boolean}
 */
Keyboard.prototype.isAnyKey = function() {
    return this._keyDownNum > 0;
};

/**
 * 判定是否有任何按键在当前帧按下
 * @return {number}
 */
Keyboard.prototype.isAnyKeyDown = function() {
    return this._anyKeyDown;
};

/**
 * 记录按键状态
 * @param keyCode {number}
 * @param isDown {boolean} 是否按下
 * @private
 */
Keyboard.prototype._recordKey = function(keyCode, isDown) {
    if (isDown) {
        this._keyDownNum++;
        this._keyCodes[keyCode] = true;
    }
    else {
        this._keyDownNum--;
        delete this._keyCodes[keyCode];
    }

};

/**
 * 设置是否可以接受键盘事件
 * @param value
 * @private
 */
Keyboard.prototype._setEnable = function(value) {
    // enabled状态为改变不做处理
    if (this._enable === value) {
        return;
    }
    this._enable = value;

    var generator = this.generator;

    // 监控键盘事件
    if (value) {
        generator.addEventListener('keydown', this._onKeyDown, false);
        generator.addEventListener('keyup', this._onKeyUp, false);
        generator.addEventListener('blur', this._onBlur, false);
        this._addFocusAbility(generator, true);
    }
    else {
        generator.removeEventListener('keydown', this._onKeyDown);
        generator.removeEventListener('keyup', this._onKeyUp);
        generator.removeEventListener('blur', this._onBlur);
        this._removeFocusAbility(generator);
        this._clearKeys();
    }
};

/**
 * 为dom设置获取键盘焦点的能力
 * @param dom {DOM} 需要获取键盘焦点能力的DOM对象
 * @param gainFocus {boolean} 是否马上获取焦点
 * @private
 */
Keyboard.prototype._addFocusAbility = function(dom, gainFocus) {
    // window为顶层window不需要
    if (dom === window && (!window.parent || window.parent === window))
        return;

    // 如果不是window对象，则添加一个tabindex的DOM，使其可以获得焦点
    dom.tabIndex = dom.tabIndex || -1;

    var self = this;
    dom.addEventListener('mouseover', self._onMouseOver, false);
    dom.addEventListener('click', self._onMouseClick, false);
    this.input.onPointerDown.add(self._onMouseClick, self);
    if (gainFocus)
    {
        setTimeout(function() {
            self.gainFocus();
        }, 1);
    }
};

/**
 * 移除dom设置的键盘焦点获取能力
 * @param dom {DOM} 需要释放获取键盘焦点能力的对象
 * @private
 */
Keyboard.prototype._removeFocusAbility = function(dom) {
    // windows不需要
    if (dom === window && (!window.parent || window.parent === window))
        return;

    dom.removeEventListener('mouseover', this._onMouseOver);
    dom.removeEventListener('click', this._onMouseClick);
    this.input.onPointerDown.remove(this._onMouseClick, this);
};

/**
 * 准备按键
 * @param keyCode
 * @private
 */
Keyboard.prototype._prepareKey = function(keyCode) {
    if (!this._keys[keyCode])
        this._keys[keyCode] = new qc.Key(this.game, keyCode);
}

/**
 * 设置修饰键状态
 * @param event {DOM.event} 点击事件
 * @param eventKey {number} 事件的原始键值
 */
Keyboard.prototype.setModifierKeysState = function(event, eventKey) {
    // 事件不存在修饰键值
    if (event.altKey === undefined)
        return;

    this._setKeyCodeState(Keyboard.ALT, event.altKey, eventKey);
    this._setKeyCodeState(Keyboard.SHIFT, event.shiftKey, eventKey);
    this._setKeyCodeState(Keyboard.CONTROL, event.ctrlKey, eventKey);
    if (eventKey != Keyboard.META &&
        eventKey != Keyboard.META_RWIN &&
        eventKey != Keyboard.META_RMAC)
    {
        this._setKeyCodeState(Keyboard.META, event.metaKey, eventKey);
    }
};

/**
 * 强制设置按键状态，不触发keydown或者keyup
 * @param keyCode {number} - 要设置的键值
 * @param isDown {boolean} - 是否按下
 * @private
 */
Keyboard.prototype._setKeyCodeState = function(keyCode, isDown, eventKey) {
    // 当强制设置的键值于事件发生的键值一致时，不进行处理
    if (keyCode === eventKey)
        return;
    this._prepareKey(keyCode);
    if (this._keys[keyCode].setState(isDown))
        this._recordKey(keyCode, isDown);
};

/**
 * 当按键按下时
 * @param event
 * @private
 */
Keyboard.prototype._processKeyDown = function(event) {
    this._prepareKey(event.keyCode);
    if (this._capture[event.keyCode]) {
        event.preventDefault();
    }
    // 记录键值，如果按键已经是按下状态，则不触发keydown事件
    this._keys[event.keyCode].onDown();

    this._keys[event.keyCode].currCharCode = event.charCode;

    // 当在失去焦点时按下功能键，缓存中没有功能键的状态
    // 补上功能键的状态，但不触发KeyDown
    this.setModifierKeysState(event, event.keyCode);

    this.input.nativeMode && this.update();
};

/**
 * 当按键弹起时
 * @param event
 * @private
 */
Keyboard.prototype._processKeyUp = function(event) {
    this._prepareKey(event.keyCode);
    if (this._capture[event.keyCode]) {
        event.preventDefault();
    }
    // 记录键值，如果按键已经是弹起状态，则不触发keyUp事件
    this._keys[event.keyCode].onUp();

    // 当在失去焦点时按下功能键，缓存中没有功能键的状态
    // 补上功能键的状态，但不触发KeyUp
    this.setModifierKeysState(event, event.keyCode);
    this.input.nativeMode && this.update();
};

/**
 * 当鼠标经过发生器时
 * @param event
 * @private
 */
Keyboard.prototype._onOver = function(event) {
    if (this.gainFocusType === Keyboard.GAINFOCUS_OVER)
        this.gainFocus();
};

/**
 * 当发生点击事件时
 * @param event
 * @private
 */
Keyboard.prototype._onClick = function(event) {
    if (this.gainFocusType === Keyboard.GAINFOCUS_CLICK)
        this.gainFocus();
};

/**
 * 得到当前窗口的输入焦点
 * @internal
 */
Keyboard.prototype.gainFocus = function(ignore) {
    // 仅当DOM不是window时可以获得焦点
    if (this.generator !== window || (window.parent && window.parent !== window))
    {
        this.generator.focus();
    }
    // TODO: 如果位置发生变化是否需要将位置还原

};

/**
 * 清理所有的事件混存
 * @private
 */
Keyboard.prototype._clearKeys = function() {
    while (this._keys.length > 0)
    {
        var key = this._keys.pop();
        if (key)
            key.destroy();
    }
    this._keyDownNum = 0;
    this._keyCodes = {};
};

/**
 * 当失去输入焦点时调用，用来释放缓存的按键信息
 * @internal
 */
Keyboard.prototype.lossFocus = function() {
    this._clearKeys();
};

/**
 * 默认处理键盘事件时，不会设置事件中止，按键事件将继续传递下去，
 * 一些特殊的功能键可能被浏览器上层捕获，比如 up，down，left，right会引起其他特殊的效果
 * 如果需要中止事件，则调用该方法设置需要中止的按键事件
 * @param keycode {number} 需要中止的事件的键值
 */
Keyboard.prototype.addKeyCapture = function(keycode) {

    if (typeof keycode === 'object')
    {
        for (var key in keycode)
        {
            this._capture[keycode[key]] = true;
        }
    }
    else
    {
        this._capture[keycode] = true;
    }
};

/**
 * 默认处理键盘事件时，将已经设置中止的键值事件中止，
 * 如果需要按键事件将继续传递下去，则调用该方法取消中止该按键事件
 * @param keyCode
 */
Keyboard.prototype.removeKeyCapture = function(keyCode) {
    delete this._capture[keyCode];
};

/**
 * 取消所有的按键事件中止设置
 */
Keyboard.prototype.clearCapture = function() {
    this._capture = {};
}

/**
 * 当事件发生器不是window时，当鼠标或者光标移动到发生器区域时获得焦点
 * @constant
 * @type {number}
 */
Keyboard.GAINFOCUS_OVER = 0;

/**
 * 当事件发生器不是window时，当点击发生器区域时获得焦点
 * @constant
 * @type {number}
 */
Keyboard.GAINFOCUS_CLICK = 1;

/**
 * 定义按键信息
 * @type {Number}
 */
Keyboard.A = "A".charCodeAt(0);
Keyboard.B = "B".charCodeAt(0);
Keyboard.C = "C".charCodeAt(0);
Keyboard.D = "D".charCodeAt(0);
Keyboard.E = "E".charCodeAt(0);
Keyboard.F = "F".charCodeAt(0);
Keyboard.G = "G".charCodeAt(0);
Keyboard.H = "H".charCodeAt(0);
Keyboard.I = "I".charCodeAt(0);
Keyboard.J = "J".charCodeAt(0);
Keyboard.K = "K".charCodeAt(0);
Keyboard.L = "L".charCodeAt(0);
Keyboard.M = "M".charCodeAt(0);
Keyboard.N = "N".charCodeAt(0);
Keyboard.O = "O".charCodeAt(0);
Keyboard.P = "P".charCodeAt(0);
Keyboard.Q = "Q".charCodeAt(0);
Keyboard.R = "R".charCodeAt(0);
Keyboard.S = "S".charCodeAt(0);
Keyboard.T = "T".charCodeAt(0);
Keyboard.U = "U".charCodeAt(0);
Keyboard.V = "V".charCodeAt(0);
Keyboard.W = "W".charCodeAt(0);
Keyboard.X = "X".charCodeAt(0);
Keyboard.Y = "Y".charCodeAt(0);
Keyboard.Z = "Z".charCodeAt(0);
Keyboard.ZERO = "0".charCodeAt(0);
Keyboard.ONE = "1".charCodeAt(0);
Keyboard.TWO = "2".charCodeAt(0);
Keyboard.THREE = "3".charCodeAt(0);
Keyboard.FOUR = "4".charCodeAt(0);
Keyboard.FIVE = "5".charCodeAt(0);
Keyboard.SIX = "6".charCodeAt(0);
Keyboard.SEVEN = "7".charCodeAt(0);
Keyboard.EIGHT = "8".charCodeAt(0);
Keyboard.NINE = "9".charCodeAt(0);
Keyboard.NUMPAD_0 = 96;
Keyboard.NUMPAD_1 = 97;
Keyboard.NUMPAD_2 = 98;
Keyboard.NUMPAD_3 = 99;
Keyboard.NUMPAD_4 = 100;
Keyboard.NUMPAD_5 = 101;
Keyboard.NUMPAD_6 = 102;
Keyboard.NUMPAD_7 = 103;
Keyboard.NUMPAD_8 = 104;
Keyboard.NUMPAD_9 = 105;
Keyboard.NUMPAD_MULTIPLY = 106;
Keyboard.NUMPAD_ADD = 107;
Keyboard.NUMPAD_ENTER = 108;
Keyboard.NUMPAD_SUBTRACT = 109;
Keyboard.NUMPAD_DECIMAL = 110;
Keyboard.NUMPAD_DIVIDE = 111;
Keyboard.F1 = 112;
Keyboard.F2 = 113;
Keyboard.F3 = 114;
Keyboard.F4 = 115;
Keyboard.F5 = 116;
Keyboard.F6 = 117;
Keyboard.F7 = 118;
Keyboard.F8 = 119;
Keyboard.F9 = 120;
Keyboard.F10 = 121;
Keyboard.F11 = 122;
Keyboard.F12 = 123;
Keyboard.F13 = 124;
Keyboard.F14 = 125;
Keyboard.F15 = 126;
Keyboard.COLON = 186;
Keyboard.EQUALS = 187;
Keyboard.UNDERSCORE = 189;
Keyboard.QUESTION_MARK = 191;
Keyboard.TILDE = 192;
Keyboard.OPEN_BRACKET = 219;
Keyboard.BACKWARD_SLASH = 220;
Keyboard.CLOSED_BRACKET = 221;
Keyboard.QUOTES = 222;
Keyboard.BACKSPACE = 8;
Keyboard.TAB = 9;
Keyboard.CLEAR = 12;
Keyboard.ENTER = 13;
Keyboard.SHIFT = 16;
Keyboard.CONTROL = 17;
Keyboard.ALT = 18;
Keyboard.META = 91;
Keyboard.META_RWIN = 92;
Keyboard.META_RMAC = 93;
Keyboard.CAPS_LOCK = 20;
Keyboard.ESC = 27;
Keyboard.SPACEBAR = 32;
Keyboard.PAGE_UP = 33;
Keyboard.PAGE_DOWN = 34;
Keyboard.END = 35;
Keyboard.HOME = 36;
Keyboard.LEFT = 37;
Keyboard.UP = 38;
Keyboard.RIGHT = 39;
Keyboard.DOWN = 40;
Keyboard.INSERT = 45;
Keyboard.DELETE = 46;
Keyboard.HELP = 47;
Keyboard.NUM_LOCK = 144;
Keyboard.PLUS = 43;
Keyboard.MINUS = 45;

/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 鼠标输入交互功能。
 * @class qc.Mouse
 * @param {qc.Input} input - 输入事件处理器
 * @param {DOM} generator -  事件的发生器，一般为DOM元素，默认为game.canvas
 * @internal
 */
var Mouse = qc.Mouse = function(input, generator) {

    /**
     * @property {qc.Input} input - 本地的交互事件处理器
     */
    this.input = input;

    /**
     * @property {qc.Game} game - 游戏对象
     */
    this.game = input.game;

    /**
     * @property {qc.Mouse~WheelEventProxy} _wheelEvent - 滚动事件的代理对象，用来统一多个浏览器之间的滚动事件
     * @private
     */
    this._wheelEvent = null;

    /**
     * @property {boolean} capture - 对于DOM的鼠标事件会调用event.preventDefault停止事件传递，否则事件会全部通知
     */
    this.capture = true;

    /**
     * @property {object} callbackContext - The context under which callbacks are called.
     */
    this.callbackContext = this.game;

    /**
     * @property {function} mouseDownCallback - A callback that can be fired when the mouse is pressed down.
     */
    this.mouseDownCallback = null;

    /**
     *
     * @property {Phaser.Signal} onMouseDown - 鼠标按下的事件
     * 通知参数：button, worldX, worldY
     */
    this.onMouseDown = new Phaser.Signal();

    /**
     * @property {Phaser.Signal} onMouseUp - 鼠标弹起的事件
     * 通知参数：button, worldX, worldY
     */
    this.onMouseUp = new Phaser.Signal();

    /**
     * @property {Phaser.Signal} onMouseMove - 鼠标在事件发生区域移动的事件
     * 通知参数：worldX, worldY
     */
    this.onMouseMove = new Phaser.Signal();

    /**
     *
     * @property {Phaser.Signal} onMousePressMove - 鼠标按下时移动的事件
     * 通知参数：button, worldX, worldY
     */
    this.onMousePressMove = new Phaser.Signal();

    /**
     * @property {Phaser.Signal} onMouseWheel - 鼠标滚轮滚动的事件
     * 通知参数：wheelDelta, wheelDeltaX, wheelDeltaY
     */
    this.onMouseWheel = new Phaser.Signal();

    /**
     * @property {[qc.Pointer]} _mouses - 鼠标事件缓存
     * @private
     */
    this._mouses = [];

    /**
     * @property {{number:boolean}} _mouseIds - 当前所有按下的鼠标Id
     * @private
     */
    this._mouseIds = {};

    /**
     * @property {number} _mouseDownNum - 记录当前按下的鼠标按键数量
     * @private
     */
    this._mouseDownNum = 0;

    /**
     * @property {boolean} _anyMouseDown - 当前帧是否有按键按下
     * @private
     */
    this._anyMouseDown = false;

    /**
     * @property {boolean} _checkEventHandledByTouch - 是否检测一个点事件是否已经被触摸事件捕获
     * 在android的某些浏览器上，触摸触发事件后仍然会触发鼠标事件
     * 需要在事件触发是向qc.Touch查询是否已经被捕获了
     * @private
     */
    this._checkEventHandledByTouch = (this.game.phaser.device.android && this.game.phaser.device.chrome === false);

    /**
     * @property {{deltaX: number, deltaY: number}} _wheelInfo - 滚动的事件
     * @private
     */
    this._wheelInfo = {
        deltaX: 0,      // x轴上的滚动距离
        deltaY: 0       // y轴上的滚动距离
    };

    // 初始化
    this._init(generator);

};
Mouse.prototype = {};
Mouse.prototype.constructor = Mouse;


Object.defineProperties(Mouse.prototype, {
    /**
     * @property {boolean} enabled - 鼠标交互开关，初始化时默认启动
     */
    enable: {
        get: function() { return this._enable; },
        set: function(value) { this._setEnable(value); }
    },

    /**
     * @property {DOM} generator -  鼠标事件的发生器，一般为HTML DOM对象
     * 当设置为空时，使用game.canvas作为事件发生器
     */
    generator: {
        get: function() { return this._generator || this.game.canvas.parentNode || this.game.canvas; },
        set: function(value) {
            // 监听对象没有修改，不进行操作
            if (value === this._generator)
                return;
            var oldState = this.enable;

            // 先注销掉之前的监听
            this.enable = false;

            // 替换发生器
            this._generator = value;

            if (oldState) {
                // 设置事件可用
                this.enable = true;
            }
        }
    },

    /**
     * @property {{deltaX: number, deltaY: number}} mouseWheel - 返回鼠标两帧之间滚动的距离
     * @readonly
     */
    mouseWheel: {
        get: function() { return this._wheelInfo; }
    },

    /**
     * @property {number} mouseCount - 返回当前鼠标按下的数量
     * @readonly
     */
    mouseCount: {
        get: function() { return this._mouseDownNum; }
    },

    /**
     * @property {[number]} mouseIdx - 返回当前所有按下的鼠标Id
     * @readonly
     */
    mouseIds: {
        get: function() { return Object.keys(this._mouseIds); }
    }
});

/**
 * 初始化
 * @param generator
 * @private
 */
Mouse.prototype._init = function(generator) {
    // 鼠标实际键值到游戏自定义键值的映射
    this._mouseKeyMap = {
        0: Mouse.BUTTON_LEFT,
        1: Mouse.BUTTON_MIDDLE,
        2: Mouse.BUTTON_RIGHT,
        3: Mouse.BUTTON_BACK,
        4: Mouse.BUTTON_FORWORD
    };
    this._mouseKeyMap[-1] = Mouse.NONE;

    // 设置监听事件接口
    this._onMouseDown = this.processMouseDown.bind(this);
    this._onMouseUp = this.processMouseUp.bind(this);
    this._onMouseOver = this.processMouseOver.bind(this);
    this._onMouseOut = this.processMouseOut.bind(this);
    this._onMouseUpGlobal = this.processMouseUpGlobal.bind(this);
    this._onMouseMove = this.processMouseMove.bind(this);
    this._onMouseWheel = this.processMouseWheel.bind(this);

    this.generator = generator;
    this.enable = true;
};

/**
 * 更新
 */
Mouse.prototype.update = function() {
    if (! this.enable)
        return;
    // 处理点击事件
    this._anyMouseDown = false;
    var i = this._mouses.length;
    var mouse;
    // 不处理Mouse.NONE
    while (i-- > 1) {
        mouse = this._mouses[i];
        if (!mouse)
            continue;

        while (mouse.nextEvent()) {
            // 按下事件
            if (mouse.isJustDown) {
                this._anyMouseDown = true;
                this._recordMouse(mouse.id, true);
                this.onMouseDown.dispatch(mouse.id, mouse.x, mouse.y);
            }
            mouse.update();
            if (mouse.isDown && (mouse.deltaX || mouse.deltaY)) {
                this.onMousePressMove.dispatch(mouse.id, mouse.x, mouse.y);
            }
            // 弹起事件
            if (mouse.isJustUp) {
                this._recordMouse(mouse.id, false);
                this.onMouseUp.dispatch(mouse.id, mouse.x, mouse.y);
            }
        }
        
    }

    // 处理移动事件
    mouse = this.getMouseById(Mouse.NONE);
    if (mouse)
    {
        while (mouse.nextEvent()) {
            mouse.update();
            if (mouse.deltaX || mouse.deltaY) {
                this.onMouseMove.dispatch(mouse.x, mouse.y);
            }
        }
    }

    // 处理滚动事件
    var tmpWheel = this._tempWheelInfo || (this._tempWheelInfo = {
            deltaX: 0,
            deltaY: 0
        });
    if (tmpWheel && (tmpWheel.deltaX || tmpWheel.deltaY)) {
        this._wheelInfo.deltaX = tmpWheel.deltaX || 0;
        this._wheelInfo.deltaY = tmpWheel.deltaY || 0;

        this.onMouseWheel.dispatch(this._wheelInfo.deltaX, this._wheelInfo.deltaY);
    }
    else {
        this._wheelInfo.deltaX = 0;
        this._wheelInfo.deltaY = 0;
    }
    tmpWheel.deltaX = 0;
    tmpWheel.deltaY = 0;
};

/**
 * 销毁
 * @internal
 */
Mouse.prototype.destroy = function() {
    this.enable = false;

    this._onMouseDown = null;
    this._onMouseUp = null;
    this._onMouseOver = null;
    this._onMouseOut = null;
    this._onMouseUpGlobal = null;
    this._onMouseMove = null;
    this._onMouseWheel = null;

    this.input = null;
    this.game = null;
    this.generator = null;
};

/**
 * 设置是否可以接受鼠标事件
 * @param value
 * @private
 */
Mouse.prototype._setEnable = function(value) {
    // enabled状态为改变不做处理
    if (this._enable === value) {
        return;
    }
    if (this.game.phaser.device.android && this.game.phaser.device.chrome === false) {
        // 安卓设备下，部分浏览器即使在touchStart中preventDefault也会触发鼠标事件，在这里进行屏蔽，不监听相关事件
        return;
    }
    this._enable = value;

    var generator = this.generator;
    var device = this.game.phaser.device;
    // 监控键盘事件
    if (value) {
        generator.addEventListener('mousedown', this._onMouseDown, true);
        generator.addEventListener('mouseup', this._onMouseUp, true);
        generator.addEventListener('mousemove', this._onMouseMove, true);

        if (!device.cocoonJS) {
            generator === window || window.addEventListener('mouseup', this._onMouseUpGlobal, true);
            generator.addEventListener('mouseout', this._onMouseOut, true);
            generator.addEventListener('mouseover', this._onMouseOver, true);
        }

        var wheelEvent = device.wheelEvent;
        if (wheelEvent) {
            generator.addEventListener(wheelEvent, this._onMouseWheel, true);

            if (wheelEvent === 'mousewheel') {
                this._wheelEvent = new WheelEventProxy(-1/40, 1);
            }
            else if (wheelEvent === 'DOMMouseScroll') {
                this._wheelEvent = new WheelEventProxy(1, 1);
            }
        }

        // 创建光标移动的事件
        this.getMouseById(Mouse.NONE);
    }
    else {
        generator.removeEventListener('mousedown', this._onMouseDown, true);
        generator.removeEventListener('mousemove', this._onMouseMove, true);
        generator.removeEventListener('mouseup', this._onMouseUp, true);
        generator.removeEventListener('mouseout', this._onMouseOut, true);
        generator.removeEventListener('mouseover', this._onMouseOver, true);

        var wheelEvent = device.wheelEvent;
        if (wheelEvent) {
            generator.removeEventListener(wheelEvent, this._onMouseWheel, true);
        }

        generator === window || window.removeEventListener('mouseup', this._onMouseUpGlobal, true);

        this._clearMouses();
    }
};

/**
 * 清理所有的事件混存
 * @private
 */
Mouse.prototype._clearMouses = function() {
    while (this._mouses.length > 0)
    {
        var mouse = this._mouses.pop();
        if (mouse)
            mouse.destroy();
    }
    this._mouseDownNum = 0;
    this._mouseIds = {};
};

/**
 * 当失去输入焦点时调用
 * @internal
 */
Mouse.prototype.lossFocus = function() {
    this._clearMouses();
    this.getMouseById(Mouse.NONE);
};

/**
 * 现在是否有鼠标处在按下状态
 * @returns {boolean}
 */
Mouse.prototype.isAnyMouse = function() {
    return this._mouseDownNum > 0;
};

/**
 * 当前帧是否有鼠标按下
 * @returns {boolean}
 */
Mouse.prototype.isAnyMouseDown = function() {
    return this._anyMouseDown;
};

/**
 * 指定鼠标是否按下
 * @param id {number} 鼠标id
 * @returns {boolean}
 */
Mouse.prototype.isMouseDown = function(id) {
    return !!this._mouseIds[id];
};

/**
 * 记录鼠标按键状态
 * @param id {number}
 * @param isDown {boolean} 是否按下
 * @private
 */
Mouse.prototype._recordMouse = function(id, isDown) {
    if (isDown) {
        this._mouseDownNum++;
        this._mouseIds[id] = true;
    }
    else {
        this._mouseDownNum--;
        delete this._mouseIds[id];
    }

};

/**
 * 通过Id获得点事件
 * @param id
 * @returns {qc.Pointer}
 */
Mouse.prototype.getMouseById = function(id) {
    var absID = Math.abs(id);
    if (!this._mouses[absID])
        this._mouses[absID] = new qc.Pointer(this.game, id);
    return this._mouses[absID];
};

/**
 * 因为鼠标只有一个，更新位置时需要更新所有点的信息
 * @param x {number} - x轴坐标
 * @param y {number} - y轴坐标
 * @param type {number} - 0：move，1：enter，2：leave
 * @private
 */
Mouse.prototype._updateMousesPosition = function(x, y, type) {
    var i = this._mouses.length;
    var mouse;
    while (i--) {
        mouse = this._mouses[i];
        if (!mouse) continue;
        if (type === qc.Pointer.ENTER) {
            mouse.onEnter(x, y);
        }
        else if (type === qc.Pointer.LEAVE) {
            mouse.onLeave(x, y);
        }
        else {
            mouse.onMove(x, y);
        }
    }
};

/**
 * 将鼠标坐标转化为World坐标
 * @param source {DOM} 事件产生的DOM对象
 * @param x {number} x轴坐标
 * @param y {number} y轴坐标
 * @return {{x: number, y: number}} 在world中的位置
 * @private
 */
Mouse.prototype._toWorld = function(source, x, y) {
    var canvas = this.game.canvas;
    var scaleFactor = this.game.phaser.scale.scaleFactor;
    var canvasBound = canvas.ownerDocument === (this.generator.ownerDocument || this.generator.document) ?
        canvas.getBoundingClientRect() :
        ((this.generator.getBoundingClientRect && this.generator.getBoundingClientRect()) || { left: 0, top: 0 });
    return {
        x: (x - canvasBound.left) * scaleFactor.x,
        y: (y - canvasBound.top) * scaleFactor.y
    };
};

/**
 * 处理鼠标的按下事件
 * @param event
 */
Mouse.prototype.processMouseDown = function(event) {
    if (!this.input.ignoreDomEvent(event) && this.capture)
        event.preventDefault();

    if (this.mouseDownCallback)
    {
        this.mouseDownCallback.call(this.callbackContext, event);
    }

    var code = this._mouseKeyMap[event.button];

    // 不支持的键值
    if (code == undefined)
        return;
    var worldPoint = this._toWorld(this.generator, event.clientX, event.clientY);

    // 检测事件是否已经被触摸事件捕获
    if (this._checkEventHandledByTouch &&
        this.input.touch.checkEventHandled(true, worldPoint.x, worldPoint.y))
        return;

    var pointer = this.getMouseById(code);
    pointer.deviceId = event.button;

    pointer.onDown(worldPoint.x, worldPoint.y);

    // 更新修饰键状态
    this.input.keyboard.setModifierKeysState(event);
    this.input.nativeMode && this.update();
};

/**
 * 处理鼠标的弹起事件
 * @param event
 */
Mouse.prototype.processMouseUp = function(event) {
    if (!this.input.ignoreDomEvent(event) && this.capture)
        event.preventDefault();

    var code = this._mouseKeyMap[event.button];

    // 不支持的键值
    if (code == undefined)
        return;
    var pointer = this.getMouseById(code);
    pointer.deviceId = event.button;
    pointer.onUp();

    // 更新修饰键状态
    this.input.keyboard.setModifierKeysState(event);
    this.input.nativeMode && this.update();
};

/**
 * 处理鼠标在区域外弹起的事件
 * @param event
 */
Mouse.prototype.processMouseUpGlobal = function(event) {
    //if (this.capture)
    //    event.preventDefault();

    var code = this._mouseKeyMap[event.button];

    // 不支持的键值
    if (code == undefined)
        return;

    var pointer = this.getMouseById(code);
    pointer.onUp();

    // 更新修饰键状态，Game被shutDown时input为空
    if (this.input && this.input.keyboard)
        this.input.keyboard.setModifierKeysState(event);
    this.input.nativeMode && this.update();
};

/**
 * 处理鼠标移出区域的事件
 * @param event
 */
Mouse.prototype.processMouseOut = function(event) {
    if (!this.input.ignoreDomEvent(event) && this.capture)
        event.preventDefault();

    var code = this._mouseKeyMap[event.button];

    // 不支持的键值
    if (code == undefined)
        return;
    var worldPoint = this._toWorld(this.generator, event.clientX, event.clientY);
    this._updateMousesPosition(worldPoint.x, worldPoint.y, qc.Pointer.LEAVE);
    this.input.nativeMode && this.update();
};

/**
 * 处理鼠标进入区域的事件
 * @param event
 */
Mouse.prototype.processMouseOver = function(event) {
    if (!this.input.ignoreDomEvent(event) && this.capture)
        event.preventDefault();

    var code = this._mouseKeyMap[event.button];

    // 不支持的键值
    if (code == undefined)
        return;
    var worldPoint = this._toWorld(this.generator, event.clientX, event.clientY);
    this._updateMousesPosition(worldPoint.x, worldPoint.y, qc.Pointer.ENTER);
    // 更新修饰键状态
    this.input.keyboard.setModifierKeysState(event);
    this.input.nativeMode && this.update();
};

/**
 * 鼠标在区域内移动的事件
 * @param event
 */
Mouse.prototype.processMouseMove = function(event) {
    if (!this.input.ignoreDomEvent(event) && this.capture)
        event.preventDefault();

    var code = this._mouseKeyMap[event.button];

    // 不支持的键值
    if (code == undefined)
        return;


    var worldPoint = this._toWorld(this.generator, event.clientX, event.clientY);
    this._updateMousesPosition(worldPoint.x, worldPoint.y, qc.Pointer.MOVE);
    // 更新修饰键状态
    this.input.keyboard.setModifierKeysState(event);
    this.input.nativeMode && this.update();
};

/**
 * 鼠标滚轮滚动的事件
 * @param event
 */
Mouse.prototype.processMouseWheel = function(event) {

    if (this._wheelEvent) {
        event = this._wheelEvent.bindEvent(event);
    }

    if (!this.input.ignoreDomEvent(event) && this.capture)
        event.preventDefault();

    // 统一firefox的事件参数
    var wheelDelta = Phaser.Math.clamp(-event.deltaY, -1, 1);
    var wheelInfo = this._tempWheelInfo || (this._tempWheelInfo = {
            deltaX: 0,
            deltaY: 0
        });
    wheelInfo.deltaX += event.deltaX;
    wheelInfo.deltaY += event.deltaY;

    // 更新修饰键状态
    this.input.keyboard.setModifierKeysState(event);
    this.input.nativeMode && this.update();
};

/**
 * 没有按下鼠标
 * @constant
 * @type {number}
 */
Mouse.NONE = 0;

/**
 * 鼠标左键
 * @constant
 * @type {number}
 */
Mouse.BUTTON_LEFT = -1;

/**
 * 鼠标中键
 * @constant
 * @type {number}
 */
Mouse.BUTTON_MIDDLE = -2;

/**
 * 鼠标右键
 * @constant
 * @type {number}
 */
Mouse.BUTTON_RIGHT = -3;

/**
 * 鼠标的后退键
 * @constant
 * @type {number}
 */
Mouse.BUTTON_BACK = -4;

/**
 * 鼠标的前进键
 * @constant
 * @type {number}
 */
Mouse.BUTTON_FORWORD = -5;


/* jshint latedef:nofunc */
/**
 * 'wheelscroll' 和 'DOMMouseWheel' 滚动事件的协同处理类
 *
 * See https://developer.mozilla.org/en-US/docs/Web/Events/mousewheel for choosing a scale and delta mode.
 *
 * @method qc.Mouse#WheelEventProxy
 * @private
 * @param {number} scaleFactor - 作用于滚动值参数的缩放比例
 * @param {integer} deltaMode - 事件的类型.
 */
function WheelEventProxy (scaleFactor, deltaMode) {

    /**
     * @property {number} _scaleFactor - 作用于滚动值参数的缩放比例.
     * @private
     */
    this._scaleFactor = scaleFactor;

    /**
     * @property {number} _deltaMode - 滚动的模式.
     * @private
     */
    this._deltaMode = deltaMode;

    /**
     * @property {any} originalEvent - 原始事件
     * @private
     */
    this.originalEvent = null;
}

WheelEventProxy.prototype = {};
WheelEventProxy.prototype.constructor = WheelEventProxy;

WheelEventProxy.prototype.bindEvent = function (event) {

    if (!WheelEventProxy._stubsGenerated && event)
    {
        var makeBinder = function (name) {

            return function () {
                var v = this.originalEvent[name];
                return typeof v !== 'function' ? v : v.bind(this.originalEvent);
            };

        };

        for (var prop in event)
        {
            if (!(prop in WheelEventProxy.prototype))
            {
                Object.defineProperty(WheelEventProxy.prototype, prop, {
                    get: makeBinder(prop)
                });
            }
        }
        WheelEventProxy._stubsGenerated = true;
    }

    this.originalEvent = event;
    return this;

};

Object.defineProperties(WheelEventProxy.prototype, {
    /**
     * @property {string} 类型
     *
     */
    "type": { value: "wheel" },

    /**
     * @property {number} 滚动模式
     */
    "deltaMode": { get: function () { return this._deltaMode; } },

    /**
     * @property {number} 在Y轴上的滚动距离
     */
    "deltaY": {
        get: function () {
            return (this._scaleFactor * (this.originalEvent.wheelDelta || this.originalEvent.detail)) || 0;
        }
    },

    /**
     * @property {number} 在X轴上的滚动距离
     */
    "deltaX": {
        get: function () {
            return (this._scaleFactor * this.originalEvent.wheelDeltaX) || 0;
        }
    },

    /**
     * @property {number} 在Z轴上的滚动距离
     */
    "deltaZ": { value: 0 }
});

/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 触摸输入交互功能。
 * @class qc.Touch
 * @param {qc.Input} input - 输入事件处理器
 * @param {DOM} generator -  事件的发生器，一般为DOM元素，默认为game.canvas
 * @internal
 */
var Touch = qc.Touch = function(input, generator) {

    /**
     * @property {qc.Input} input - 本地的交互事件处理器
     */
    this.input = input;

    /**
     * @property {qc.Game} game - 游戏对象
     */
    this.game = input.game;

    /**
     * @property {boolean} capture - 对于DOM的触摸事件会调用event.preventDefault停止事件传递，否则事件会全部通知
     */
    this.capture = true;

    /**
     * @property {Phaser.Signal} onTouchStart - 触摸开始的事件
     * 通知参数：id, worldX, worldY
     */
    this.onTouchStart = new Phaser.Signal();

    /**
     * @property {Phaser.Signal} onTouchEnd - 触摸结束的事件
     * 通知参数：id, worldX, worldY
     */
    this.onTouchEnd = new Phaser.Signal();

    /**
     * @property {Phaser.Signal} onTouchMove - 触摸移动的事件
     * 通知参数：worldX, worldY
     */
    this.onTouchMove = new Phaser.Signal();

    /**
     * @property {[id: qc.Pointer]} _touches - 触摸事件缓存
     * @private
     */
    this._touches = [];

    /**
     * @property {{number:boolean}} _touchIds - 当前所有触摸事件id
     * @private
     */
    this._touchIds = {};

    /**
     * @property {{{number}: {nubmer}}} _deviceIdMap - deviceId和id的对应表
     * @private
     */
    this._deviceIdMap = {};


    /**
     * @property {[{x:{number},y:{number},isDown:{boolean}]} _touchesStartPosition - 在android的某些浏览器上，触摸触发事件后仍然会触发鼠标事件
     * 每帧留下一份起始点存根，用来排除这类重复调用
     * 不直接屏蔽掉接收鼠标事件是为了兼容可以外接鼠标的android设备
     * @private
     */
    this._touchesStartPosition = [];

    /**
     * @property {number} _touchStartNum - 当前的触摸点数量
     * @private
     */
    this._touchStartNum = 0;

    /**
     * @property {boolean} _anyTouchStart - 当前帧是否有触摸事件触发
     * @private
     */
    this._anyTouchStart = false;

    // 初始化
    this._init(generator);

    /**
     * @property {object} callbackContext - The context under which callbacks are called.
     */
    this.callbackContext = this.game;

    /**
     * @property {function} touchStartCallback - A callback that can be fired on a touchStart event.
     */
    this.touchStartCallback = null;

    /**
     * @property {function} touchMoveCallback - A callback that can be fired on a touchMove event.
     */
    this.touchMoveCallback = null;

    /**
     * @property {function} touchEndCallback - A callback that can be fired on a touchEnd event.
     */
    this.touchEndCallback = null;

    /**
     * @property {function} touchEnterCallback - A callback that can be fired on a touchEnter event.
     */
    this.touchEnterCallback = null;

    /**
     * @property {function} touchLeaveCallback - A callback that can be fired on a touchLeave event.
     */
    this.touchLeaveCallback = null;

    /**
     * @property {function} touchCancelCallback - A callback that can be fired on a touchCancel event.
     */
    this.touchCancelCallback = null;
};
Touch.prototype = {};
Touch.prototype.constructor = Touch;


Object.defineProperties(Touch.prototype, {
    /**
     * @property {boolean} enabled - 触摸交互开关，初始化时默认启动
     */
    enable: {
        get: function() { return this._enable; },
        set: function(value) { this._setEnable(value); }
    },

    /**
     * @property {DOM} generator - 触摸事件的发生器，一般为HTML DOM对象
     * 当设置为空时，使用game.canvas作为事件发生器
     */
    generator: {
        get: function() { return this._generator || this.game.canvas.parentNode || this.game.canvas; },
        set: function(value) {
            // 监听对象没有修改，不进行操作
            if (value === this._generator)
                return;

            var oldState = this.enable;
            // 先注销掉之前的监听
            this.enable = false;

            // 替换发生器
            this._generator = value;

            if (oldState) {
                // 设置事件可用
                this.enable = true;
            }
        }
    },

    /**
     * @property {number} touchCount - 返回当前触摸点的数量
     * @readonly
     */
    touchCount: {
        get: function() { return this._touchStartNum; }
    },

    /**
     * @property {[number]} touchIds - 返回当前所有触摸事件Id
     * @readonly
     */
    touchIds: {
        get: function() { return Object.keys(this._touchIds); }
    }
});

/**
 * 初始化
 * @param generator
 * @private
 */
Touch.prototype._init = function(generator) {
    // 设置监听事件接口
    this._onTouchStart = this.processTouchStart.bind(this);
    this._onTouchCancel = this.processTouchCancel.bind(this);
    this._onTouchEnd = this.processTouchEnd.bind(this);
    this._onTouchEnter = this.processTouchEnter.bind(this);
    this._onTouchLeave = this.processTouchLeave.bind(this);
    this._onTouchMove = this.processTouchMove.bind(this);

    this.generator = generator;
    this.enable = true;
};

/**
 * 更新
 */
Touch.prototype.update = function() {
    if (! this.enable)
        return;
    this._touchesStartPosition.splice(0, this._touchesStartPosition.length);
    this._anyTouchStart = false;
    var i = this._touches.length;
    var touch;
    while (i--) {
        touch = this._touches[i];
        if (!touch)
            continue;

        while (touch.nextEvent()) {
            // 开始事件
            if (touch.isJustDown) {
                this._anyTouchStart = true;
                this._recordTouch(touch.id, true);
                this.onTouchStart.dispatch(touch.id, touch.x, touch.y);
            }

            touch.update();

            // 派发移动事件
            if (touch.isDown && (touch.deltaX || touch.deltaY)) {
                this.onTouchMove.dispatch(touch.id, touch.x, touch.y);
            }

            // 结束事件
            if (touch.isJustUp) {
                this._recordTouch(touch.id, false);
                this.onTouchEnd.dispatch(touch.id, touch.x, touch.y);
            }
            if (!touch.isDown) {
                this._removeTouch(touch.id);
            }
        }
    }
};

/**
 * 销毁
 * @internal
 */
Touch.prototype.destroy = function() {
    this.enable = false;

    this._onTouchStart = null;
    this._onTouchCancel = null;
    this._onTouchEnd = null;
    this._onTouchEnter = null;
    this._onTouchLeave = null;
    this._onTouchMove = null;

    this.input = null;
    this.game = null;
    this.generator = null;
};

/**
 * 检测一个点事件是否已经被触摸事件捕获
 * 在android的某些浏览器上，触摸触发事件后仍然会触发鼠标事件
 * 每帧留下一份起始点存根，用来排除这类重复调用
 * 不直接屏蔽掉接收鼠标事件是为了兼容可以外接鼠标的android设备
 * @param isDown {boolean} - 是否是按下事件
 * @param x {number} - 事件发生的x坐标
 * @param y {number} - 事件发生的y坐标
 */
Touch.prototype.checkEventHandled = function(isDown, x, y) {
    var i = this._touchesStartPosition.length;
    while (i--) {
        var position = this._touchesStartPosition[i];
        if (position.isDown === isDown &&
            position.x === x &&
            position.y === y)
            return true;
    }
    return false;
};

/**
 * 设置是否可以接受触摸事件
 * @param value
 * @private
 */
Touch.prototype._setEnable = function(value) {
    // enabled状态为改变不做处理
    if (this._enable === value) {
        return;
    }
    var generator = this.generator;
    var device = this.game.phaser.device;
    if (!device.touch) {
        this._enable = false;
        return;
    }

    this._enable = value;

    // 监控键盘事件
    if (value) {
        generator.addEventListener('touchstart', this._onTouchStart, false);
        generator.addEventListener('touchmove', this._onTouchMove, false);
        generator.addEventListener('touchend', this._onTouchEnd, false);
        generator.addEventListener('touchcancel', this._onTouchCancel, false);

        if (!device.cocoonJS) {
            generator.addEventListener('touchenter', this._onTouchEnter, false);
            generator.addEventListener('touchleave', this._onTouchLeave, false);
        }
    }
    else {
        generator.removeEventListener('touchstart', this._onTouchStart);
        generator.removeEventListener('touchmove', this._onTouchMove);
        generator.removeEventListener('touchend', this._onTouchEnd);
        generator.removeEventListener('touchcancel', this._onTouchCancel);
        generator.removeEventListener('touchenter', this._onTouchEnter);
        generator.removeEventListener('touchleave', this._onTouchLeave);

        this._clearTouches();
    }
};

/**
 * 清理所有的事件混存
 * @private
 */
Touch.prototype._clearTouches = function() {
    var touch;
    while (this._touches.length > 0)
    {
        touch = this._touches.pop();
        if (touch)
            touch.destroy();
    }
    this._deviceIdMap = {};
    this._touchStartNum = 0;
    this._touchIds = {};
};

/**
 * 当失去输入焦点时调用
 * @internal
 */
Touch.prototype.lossFocus = function() {
    this._clearTouches();
};

/**
 * 现在是否有触摸处在开始状态
 * @returns {boolean}
 */
Touch.prototype.isAnyTouch = function() {
    return this._touchStartNum > 0;
};

/**
 * 当前帧是否有触摸事件开始
 * @returns {boolean}
 */
Touch.prototype.isAnyTouchStart = function() {
    return this._anyTouchStart;
};

/**
 * 指定id的触摸事件是否开始
 * @param id
 * @returns {boolean}
 */
Touch.prototype.isTouchStart = function(id) {
    return !!this._touchIds[id];
};

/**
 * 记录触摸事件状态
 * @param id {number}
 * @param isStart {boolean} 是否开始
 * @private
 */
Touch.prototype._recordTouch = function(id, isStart) {
    if (isStart) {
        this._touchStartNum++;
        this._touchIds[id] = true;
    }
    else {
        this._touchStartNum--;
        delete this._touchIds[id];
    }

};

/**
 * 通过Id获得点事件
 * @param id
 * @returns {qc.Pointer}
 */
Touch.prototype.getTouchById = function(id) {
    return this._touches[id];
};

/**
 * 返回触摸列表中第一个无效的触摸点
 * @returns {qc.Pointer}
 * @private
 */
Touch.prototype._getFirstInvalidTouch = function() {
    var idx = -1;
    var len = this._touches.length + 1;
    while (++idx < len) {
        var touch = this._touches[idx];
        if (!touch) {
            break;
        }
        if (!touch.isDown && !touch.hasEvent()) {
            // 没有事件等待处理，并且不是按下状态则可以复用
            return touch;
        }
    }
    var pointer = new qc.Pointer(this.game, idx);
    this._touches[idx] = pointer;
    return pointer;
};

/**
 * 移除一个触摸点
 * @param id
 * @private
 */
Touch.prototype._removeTouch = function(id) {
    var pointer = this._touches[id];
    if (pointer) {
        delete this._deviceIdMap[pointer.deviceId];
    }
};

/**
 * 通过设备id获取触摸点
 * @param deviceId {number} 设备Id
 * @param create {boolean} 是否创建触摸点
 * @returns {qc.Pointer}
 */
Touch.prototype.getTouchByDeviceId = function(deviceId, create) {
    if (this._deviceIdMap[deviceId] === undefined) {
        if (create) {
            var pointer = this._getFirstInvalidTouch();
            pointer.deviceId = deviceId;
            this._deviceIdMap[deviceId] = pointer.id;
            return pointer;
        }
        return null;
    }
    return this.getTouchById(this._deviceIdMap[deviceId]);
};

/**
 * 将坐标转化为World坐标
 * @param source {DOM} 事件产生的DOM对象
 * @param x {number} x轴坐标
 * @param y {number} y轴坐标
 * @return {{x: number, y: number}} 在world中的位置
 * @private
 */
Touch.prototype._toWorld = function(source, x, y) {
    var canvas = this.game.canvas;
    var scaleFactor = this.game.phaser.scale.scaleFactor;
    var canvasBound = canvas.ownerDocument === (this.generator.ownerDocument || this.generator.document) ?
        canvas.getBoundingClientRect() :
        ((this.generator.getBoundingClientRect && this.generator.getBoundingClientRect()) || { left: 0, top: 0 });
    return {
        x: (x - canvasBound.left) * scaleFactor.x,
        y: (y - canvasBound.top) * scaleFactor.y
    };
};

/**
 * 根据事件中的touches信息更新已有信息
 * @param touches {[DOM.touch]}
 * @private
 */
Touch.prototype._updateTouchWithTouches = function(touches) {
    // 处理已有点信息
    var i = touches.length;
    var existsTouch = {};
    while (i--) {
        var touch = touches[i];
        existsTouch[touch.identifier] = true;
        var worldPoint = this._toWorld(this.generator, touch.clientX, touch.clientY);
        var pointer = this.getTouchByDeviceId(touch.identifier);
        if (pointer) {
            // 已有的触摸点，处理更新位置
            pointer.deviceId = touch.identifier;
            pointer.onMove(worldPoint.x, worldPoint.y);
        }
    }

    // 处理遗失的点信息
    var i = this._touches.length;
    while (i--) {
        var touch = this._touches[i];
        // 无效的点，存在的点，和正在处理弹起的点 为正常
        if (!touch ||
            existsTouch[touch.deviceId])
            continue;
        touch.onCancel(false);
    }
};

/**
 * 处理触摸的开始事件
 * @param event
 */
Touch.prototype.processTouchStart = function(event) {
    if (this.touchStartCallback)
    {
        this.touchStartCallback.call(this.callbackContext, event);
    }

    if (!this.input.ignoreDomEvent(event) && this.capture)
        event.preventDefault();

    // 处理事件触发的触摸点
    var i = event.changedTouches.length;
    while (i--) {
        var touch = event.changedTouches[i];
        if (!touch)
            continue;

        var pointer = this.getTouchByDeviceId(touch.identifier, true);
        var worldPoint = this._toWorld(this.generator, touch.clientX, touch.clientY);

        // 记下点击事件
        this._touchesStartPosition.push({
            isDown: true,
            x: worldPoint.x,
            y: worldPoint.y
        });

        pointer.onDown(worldPoint.x, worldPoint.y);
    }

    // 更新已有点信息
    this._updateTouchWithTouches(event.touches);

    // 更新修饰键状态
    this.input.keyboard.setModifierKeysState(event);
    this.input.nativeMode && this.update();
};

/**
 * 处理触摸的结束事件
 * @param event
 */
Touch.prototype.processTouchEnd = function(event) {
    if (this.touchEndCallback)
    {
        this.touchEndCallback.call(this.callbackContext, event);
    }

    if (!this.input.ignoreDomEvent(event) && this.capture)
        event.preventDefault();

    // 处理事件触发的触摸点
    var i = event.changedTouches.length;
    while (i--) {
        var touch = event.changedTouches[i];
        if (!touch)
            continue;

        var pointer = this.getTouchByDeviceId(touch.identifier);
        if (!pointer) {
            continue;
        }
        var worldPoint = this._toWorld(this.generator, touch.clientX, touch.clientY);

        // 记下点击事件
        this._touchesStartPosition.push({
            isDown: false,
            x: worldPoint.x,
            y: worldPoint.y
        });

        pointer.onUp(worldPoint.x, worldPoint.y);
    }

    // 更新已有点信息
    this._updateTouchWithTouches(event.touches);

    // 更新修饰键状态
    this.input.keyboard.setModifierKeysState(event);
    this.input.nativeMode && this.update();
};

/**
 * 处理触摸的取消事件
 * @param event
 */
Touch.prototype.processTouchCancel = function(event) {
    if (this.touchCancelCallback)
    {
        this.touchCancelCallback.call(this.callbackContext, event);
    }

    // 取消当做结束处理
    this.processTouchEnd(event, true);
};

/**
 * 处理触摸移出区域的事件
 * @param event
 */
Touch.prototype.processTouchLeave = function(event) {
    if (this.touchLeaveCallback)
    {
        this.touchLeaveCallback.call(this.callbackContext, event);
    }
    // 移动处理
    this.processTouchMove(event, true);
};

/**
 * 处理触摸进入区域的事件
 * @param event
 */
Touch.prototype.processTouchEnter = function(event) {
    if (this.touchEnterCallback)
    {
        this.touchEnterCallback.call(this.callbackContext, event);
    }

    // 移动处理
    this.processTouchMove(event, true);
};

/**
 * 触摸点在区域内移动的事件
 * @param event
 */
Touch.prototype.processTouchMove = function(event, skipCallback) {
    if (!skipCallback && this.touchMoveCallback)
    {
        this.touchMoveCallback.call(this.callbackContext, event);
    }

    if (!this.input.ignoreDomEvent(event) && this.capture)
        event.preventDefault();

    // 处理事件触发的触摸点
    var i = event.changedTouches.length;
    while (i--) {
        var touch = event.changedTouches[i];
        if (!touch)
            continue;

        var pointer = this.getTouchByDeviceId(touch.identifier);
        if (!pointer)
            continue;

        var worldPoint = this._toWorld(this.generator, touch.clientX, touch.clientY);
        pointer.onMove(worldPoint.x, worldPoint.y);
    }

    // 更新已有点信息
    this._updateTouchWithTouches(event.touches);
    // 更新修饰键状态
    this.input.keyboard.setModifierKeysState(event);
    this.input.nativeMode && this.update();
};

/**
 * 主触摸事件Id
 * @type {number}
 * @constant
 */
Touch.MAIN = 0;
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 基本交互的操作的事件
 * @class qc.BaseInputEvent
 * @param {qc.Key|qc.Pointer|object} source - 事件产生的源
 * @constructor
 */
var BaseInputEvent = qc.BaseInputEvent = function(source) {
    /**
     * @property {qc.Key|qc.Pointer|object} source - 事件产生的源
     */
    this.source = source;

    /**
     * @property {boolean} effect
     */
    this.effect = true;
};
BaseInputEvent.prototype = {};
BaseInputEvent.prototype.constructor = BaseInputEvent;

/**
 * 滚轮滚动事件
 * @class qc.WheelEvent
 * @param {number} deltaX - 在x轴上滚动的距离
 * @param {number} deltaY - 在y轴上滚动的距离
 * @constructor
 */
var WheelEvent = qc.WheelEvent = function(deltaX, deltaY) {
    // 继承至BaseInputEvent
    BaseInputEvent.call(this, {deltaX: deltaX, deltaY: deltaY});
};
WheelEvent.prototype = Object.create(BaseInputEvent.prototype);
WheelEvent.prototype.constructor = WheelEvent;

Object.defineProperties(WheelEvent.prototype, {
    /**
     * @property {number} deltaX - 获取x轴上滚动的距离
     * @readonly
     */
    deltaX: {
        get: function() { return this.source.deltaX; }
    },

    /**
     * @property {number} deltaY - 获取y轴上滚动的距离
     * @readonly
     */
    deltaY: {
        get: function() { return this.source.deltaY; }
    }
});

/**
 * 光标移动事件
 * @class qc.CursorMoveEvent
 * @param {number} x - 光标移动到的x轴坐标
 * @param {number} y - 光标移动到的y轴坐标
 * @constructor
 */
var CursorMoveEvent = qc.CursorMoveEvent = function(x, y) {
    // 继承至BaseInputEvent
    BaseInputEvent.call(this, {x: x, y: y});
};
CursorMoveEvent.prototype = Object.create(BaseInputEvent.prototype);
CursorMoveEvent.prototype.constructor = CursorMoveEvent;

Object.defineProperties(CursorMoveEvent.prototype, {
    /**
     * @property {number} x - 获取光标的x轴坐标
     * @readonly
     */
    x: {
        get: function() { return this.source.x; }
    },

    /**
     * @property {number} y - 获取光标的y轴坐标
     * @readonly
     */
    y: {
        get: function() { return this.source.y; }
    }
});

/**
 * 点击移动事件
 * @class qc.PointerEvent
 * @param {qc.Pointer} source -  点击事件的源
 * @constructor
 */
var PointerEvent = qc.PointerEvent = function(source) {
    // 继承至BaseInputEvent
    BaseInputEvent.call(this, source);
};
PointerEvent.prototype = Object.create(BaseInputEvent.prototype);
PointerEvent.prototype.constructor = PointerEvent;

/**
 * 点击事件
 */
var ClickEvent = qc.ClickEvent = function(source) {
    // 继承至PointerEvent
    PointerEvent.call(this, source);
    this.isTap = false;
    this.isDoubleTap = false;
    this.isDoubleClick = false;
};
ClickEvent.prototype = Object.create(PointerEvent.prototype);
ClickEvent.prototype.constructor = ClickEvent;

/**
 * 拖拽开始事件
 * @class qc.DragDropEvent
 * @param {qc.Pointer} source - 产生拖拽的点击事件
 * @construct
 */
var DragStartEvent = qc.DragStartEvent = function(source) {
    // 继承至PointerEvent
    PointerEvent.call(this, source);

    /**
     * @property {boolean} started - 是否开始拖拽
     */
    this.started = true;
};
DragStartEvent.prototype = Object.create(PointerEvent.prototype);
DragStartEvent.prototype.constructor = DragStartEvent;

/**
 * 拖拽事件
 * @class qc.DragDropEvent
 * @param {qc.Pointer} source - 产生拖拽的点击事件
 * @construct
 */
var DragEvent = qc.DragEvent = function(source) {
    // 继承至PointerEvent
    PointerEvent.call(this, source);
};
DragEvent.prototype = Object.create(PointerEvent.prototype);
DragEvent.prototype.constructor = DragEvent;

/**
 * 拖拽结束的事件
 * @class qc.DragEndEvent
 * @param {qc.Pointer} source -  产生拖拽的点击事件
 * @param {*} result - 拖拽到接收方的处理结果
 * @construct
 */
var DragEndEvent = qc.DragEndEvent = function(source, result) {
    // 继承至PointerEvent
    PointerEvent.call(this, source);

    /**
     * @property {*} result - 拖拽处理的结果
     */
    this.result = result;
};
DragEndEvent.prototype = Object.create(PointerEvent.prototype);
DragEndEvent.prototype.constructor = DragEndEvent;

/**
 * 拖拽放下事件
 * @class qc.DropEvent
 * @param {qc.Pointer} source -  产生拖拽的点击事件
 * @param {qc.Node} dragging - 被拖拽的节点
 * @construct
 */
var DropEvent = qc.DropEvent = function(source, dragging) {
    // 继承至PointerEvent
    PointerEvent.call(this, source);
    /**
     * @property {qc.Node} dragging - 被拖拽的节点
     */
    this.dragging = dragging;

    /**
     * @property {*} result - 记录拖拽放下的处理结果
     */
    this.result = null;
};
DropEvent.prototype = Object.create(PointerEvent.prototype);
DropEvent.prototype.constructor = DropEvent;
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 输入触摸模块，将简单的系统事件转化为各类操作事件，例如 click，drag等
 *
 * @class qc.BaseInputModule
 * @param {qc.Game} game - 游戏对象
 * @param {qc.Input} input - 输入对戏那个
 * @constructor
 */
var BaseInputModule = qc.BaseInputModule = function(game, input) {
    /**
     * @property {qc.Game} game - 游戏对象
     */
    this.game = game;

    /**
     * @property {qc.Input} input - 输入管理对象
     */
    this.input = input;

    /**
     * @property {boolean} handleTouchAsMouse - 将触摸事件转化为鼠标事件使用，当设置为true时，将第一个触摸点作为鼠标左键使用，
     * 当两个事件同时发生时，取第一个处理的事件作为鼠标左键事件，忽略后来者
     */
    this.handleTouchAsMouse = true;

    /**
     * @property {number} _currMainPointerId - 当handleTouchAsMouse时，用来记录当前作为主键的设备Id
     * @private
     */
    this._currMainPointerId = NaN;

    /**
     * @property {[string]} _needHandleEventTypes - 需要处理的事件列表
     * @private
     */
    this._needHandleEventTypes = ['onDown', 'onUp', 'onClick'];

    /**
     * @property {[string]} _needHandleDragTypes - 需要处理的拖拽事件列表
     * @private
     */
    this._needHandleDragTypes = ['onDragStart', 'onDrag', 'onDragEnd'];

    /**
     * @property {[string]} _needHandleClickAndDragType - 需要处理点击和拖拽事件列表
     * @type {Array}
     */
    this._needHandleClickAndDragType = ['onClick', 'onDragStart', 'onDrag', 'onDragEnd'];

    /**
     * @property {Number} _startDragMoveDistance - 开始处理拖拽需要移动的最小距离
     * @private
     */
    this._startDragMoveDistance = 10;

    /**
     * @property {Number} tapDuringTime - 轻敲事件允许的最大持续时间
     */
    this.tapDuringTime = 200;

    /**
     * @property {Number} doubleClickTime - 双击时间允许的最大间隔时间
     */
    this.doubleClickDuringTime = 600;

    /**
     * @property {{}} _pointersEvent - 记录当前处理中的事件
     * @private
     */
    this._pointersEvent = {
    };

    // 调用回调
    this._init();
};
BaseInputModule.prototype = {};
BaseInputModule.prototype.constructor = BaseInputModule;

/**
 * 初始化处理
 * @private
 */
BaseInputModule.prototype._init = function() {
    // 监听基础事件
    this.input.onPointerDown.add(this.onPointerDown, this);
    this.input.onPointerUp.add(this.onPointerUp, this);
    this.input.onPointerMove.add(this.onPointerMove, this);
    this.input.onWheel.add(this.onWheel, this);
    this.input.onCursorMove.add(this.onCursorMove, this);
};

/**
 * 创建一个点事件记录对象
 * @param deviceId
 * @returns {{pointerId: Number, deviceId: Number, downNode: null, draggingNode: null, overNode: null}}
 * @private
 */
BaseInputModule.prototype._buildPointerRecord = function(deviceId, isMouse) {
    return {

        /**
         * @property {number} pointerId - 当前事件Id
         */
        eventId : deviceId,

        /**
         * @property {number} deviceId - 当前设备Id
         */
        deviceId : deviceId,

        /**
         * @property {boolean} isMouse - 是否是鼠标事件
         */
        isMouse : isMouse,

        /**
         * @property {qc.Node | null} downNode - 位于触摸点下的节点
         */
        downNode : null,

        /**
         * @property {qc.Node | null} draggingNode - 当前拖拽的节点
         */
        draggingNode : null,

        /**
         * @property {qc.Node | null} overNode - 光标移动经过的节点
         */
        overNode : null
    };
};

/**
 * 销毁
 */
BaseInputModule.prototype.destroy = function() {
    // 取消事件监听
    this.input.onPointerDown.remove(this.onPointerDown, this);
    this.input.onPointerUp.remove(this.onPointerUp, this);
    this.input.onPointerMove.remove(this.onPointerMove, this);
    this.input.onWheel.remove(this.onWheel, this);
    this.input.onCursorMove.remove(this.onCursorMove, this);

    this.reset();
};

/**
 * 当失去输入焦点时调用，用来释放缓存的按键信息
 * @internal
 */
BaseInputModule.prototype.lossFocus = function() {
    this.reset();
};

/**
 * 更新
 */
BaseInputModule.prototype.update = function() {

};

/**
 * 重置当前的事件状态
 */
BaseInputModule.prototype.reset = function() {
    // 取消所有记录的事件
    for (var eventId in this._pointersEvent) {
        var event = this._pointersEvent[eventId];
        var pointer;
        if (!isNaN(event.deviceId)) {
            pointer = this.input.getPointer(event.deviceId);
            event.deviceId = NaN;
        }

        if (event.downNode !== null) {
            var upEvent = new qc.PointerEvent(pointer);
            event.downNode.fireInputEvent('onUp', upEvent);
            event.downNode = null;
        }

        // 如果有拖拽对象，则取消拖拽
        if (event.draggingNode) {
            var dragEndEvent = new qc.DragEndEvent(pointer, null);
            event.draggingNode.fireInputEvent('onDragEnd', dragEndEvent);
            event.draggingNode = null;
        }

        // 如果有经过的对象，则调用移出
        if (event.overNode) {
            var cursorEvent = new qc.CursorMoveEvent(NaN, NaN);
            this.doPointerExitAndEnter(event, cursorEvent, null);
            event.overNode = null;
        }
    }
    this._pointersEvent = {};
    this._currMainPointerId = NaN;
};

/**
 * 检测记录点是否已经 destroy
 * @param event
 */
BaseInputModule.prototype.checkEventRecordNode = function(event) {
    if (event.draggingNode && event.draggingNode._destroy) {
        event.draggingNode = null;
    }
    if (event.downNode && event.downNode._destroy) {
        event.downNode = null;
    }
    if (event.overNode && event.overNode._destroy) {
        event.overNode = null;
    }
};

/**
 * 当一个发生一个点下事件时的处理
 * @param id {number} - 事件设备Id
 * @param x {number} - 事件发生的x轴坐标
 * @param y {number} - 事件发生的y轴坐标
 */
BaseInputModule.prototype.onPointerDown = function(id, x, y) {
    var pointer = this.input.getPointer(id);
    var event = this._pointersEvent[id] ||
        (this._pointersEvent[id] = this._buildPointerRecord(id, this.input.isMouse(id)));

    // 当将触摸事件转化为鼠标事件时，onDown，onUp，onClick，onDragStart只被左键点击或者第一个触摸点触发
    if (this.handleTouchAsMouse) {
        // 将左键和第一个触摸点作为左键处理
        if (id === qc.Mouse.BUTTON_LEFT ||
            id === qc.Touch.MAIN) {
            // 当前是否已经有一个鼠标左键事件在处理，并且不是当前事件的设备Id，则不触发任何事件
            if (!isNaN(this._currMainPointerId) && this._currMainPointerId !== id)
                return;
            this._currMainPointerId = id;
            // 该模式下，设置事件Id为鼠标左键Id
            pointer.eventId = qc.Mouse.BUTTON_LEFT;
            event.eventId = qc.Mouse.BUTTON_LEFT;
        }
    }
    this.checkEventRecordNode(event);

    // 获取当前点击位置下的物件
    var node = this._fetchNodeInParent(this.game.world, x, y);

    // 当不是鼠标事件时，额外处理进入离开事件
    if (!event.isMouse) {
        var overEvent = new qc.CursorMoveEvent(x, y);
        this.doPointerExitAndEnter(event, overEvent, node);
    }

    var currNode = node ? node.getInputEventHandle(this._needHandleEventTypes) : null;
    // 没有响应的节点
    if (!currNode) {
        return;
    }

    // 获取节点中可以触发按下的节点并处理
    event.downNode = currNode;
    var pointEvent = new qc.PointerEvent(pointer);
    // 执行按下事件
    if (currNode) {
        currNode.fireInputEvent('onDown', pointEvent);
    }
};

/**
 * 当发生一个弹起事件时的处理
 * @param id {number} - 设备Id
 * @param x {number} - 事件发生的x轴坐标
 * @param y {number} - 事件发生的y轴坐标
 */
BaseInputModule.prototype.onPointerUp = function(id, x, y) {
    var pointer = this.input.getPointer(id);
    var event = this._pointersEvent[id]  ||
        (this._pointersEvent[id] = this._buildPointerRecord(id, this.input.isMouse(id)));
    // 当将触摸事件转化为鼠标事件时，onDown，onUp，onClick，onDragStart只被左键点击或者第一个触摸点触发
    if (this.handleTouchAsMouse) {
        // 将左键和第一个触摸点作为左键处理
        if (id === qc.Mouse.BUTTON_LEFT ||
            id === qc.Touch.MAIN) {
            // 当前是否已经一个鼠标左键事件在处理，并且不是当前事件的设备Id，则不触发任何事件
            if (this._currMainPointerId !== id)
                return;
            this._currMainPointerId = NaN;
            // 该模式下，设置事件Id为鼠标左键Id
            pointer.eventId = qc.Mouse.BUTTON_LEFT;
            event.eventId = pointer.eventId;
        }
    }
    this.checkEventRecordNode(event);

    // 获取当前点击位置下的物件
    var node = this._fetchNodeInParent(this.game.world, x, y, event.draggingNode);
    var currNode = node ? node.getInputEventHandle(this._needHandleEventTypes) : null;
    var upEvent = new qc.PointerEvent(pointer);

    // 优先处理拖拽事件
    if (event.draggingNode !== null) {
        // 作为拖拽的接收对象时，不考虑点击事件
        var dropNode = node ? node.getInputEventHandle('onDragDrop') : null;
        var dropEvent = new qc.DropEvent(pointer, event.draggingNode);
        // 接收对象是否运行当前物件拖拽到
        if (dropNode && dropNode.checkAllowDrop(event.draggingNode)) {
            dropNode.fireInputEvent('onDragDrop', dropEvent);
        }

        // 处理被拖拽物体的回调
        var dragEndEvent = new qc.DragEndEvent(pointer, dropEvent.result);
        event.draggingNode.fireInputEvent('onDragEnd', dragEndEvent);
        event.draggingNode = null;
    }
    else {
        // 获取当前点击位置下的物件
        if (currNode && currNode === event.downNode) {
            var clickEvent = new qc.ClickEvent(pointer);
            if (pointer.previousStatus.length) {
                var stat = pointer.previousStatus[0];
                var previousStat = pointer.previousStatus[1];
                clickEvent.isTap = stat.upTime - stat.downTime < this.tapDuringTime;
                clickEvent.isDoubleTap = previousStat && (stat.upTime - previousStat.downTime) < 2 * this.tapDuringTime;
                clickEvent.isDoubleClick = previousStat && (stat.upTime - previousStat.downTime) < this.doubleClickDuringTime;
            }
            // 当按下的节点和弹起的节点相同时，触发click事件
            currNode.fireInputEvent('onClick', clickEvent);
        }
    }
    // 处理弹起事件
    if (event.downNode) {
        upEvent.downInside = true;
        upEvent.upInside = event.downNode === currNode;
        event.downNode.fireInputEvent('onUp', upEvent);
    }
    if (currNode && currNode !== event.downNode) {
        upEvent.downInside = false;
        upEvent.upInside = true;
        currNode.fireInputEvent('onUp', upEvent);
    }
    event.downNode = null;

    // 当不是鼠标事件时，额外处理进入离开事件
    if (!event.isMouse) {
        var overEvent = new qc.CursorMoveEvent(x, y);
        this.doPointerExitAndEnter(event, overEvent, null);
    }

    // 移除事件记录
    delete this._buildPointerRecord[event.deviceId];
};

/**
 * 点事件移动
 * @param id
 * @param x
 * @param y
 */
BaseInputModule.prototype.onPointerMove = function(id, x, y) {
    var pointer = this.input.getPointer(id);
    var event = this._pointersEvent[id]  ||
        (this._pointersEvent[id] = this._buildPointerRecord(id, this.input.isMouse(id)));

    this.checkEventRecordNode(event);

    // 获取当前点击位置下的物件
    var node = this._fetchNodeInParent(this.game.world, x, y);

    // 当不是鼠标事件时，额外处理进入离开事件
    if (!event.isMouse) {
        var overEvent = new qc.CursorMoveEvent(x, y);
        this.doPointerExitAndEnter(event, overEvent, node);
    }

    if (!event.draggingNode) {
        var startNode = this._fetchNodeInParent(this.game.world, pointer.startX, pointer.startY);
        var tmp = { x : pointer.x, y : pointer.y };
        var currNode = null;
        do {
            if (!startNode)
                break;
            var nodeAndEventType = startNode.getInputEventHandleAndEventType(this._needHandleClickAndDragType);
            if (!nodeAndEventType)
                break;
            // 如果节点优先响应点击时，需要移动距离大于阀值
            if (nodeAndEventType[1] === 'onClick' &&
                (Math.abs(pointer.distanceX) < this._startDragMoveDistance ||
                Math.abs(pointer.distanceY) < this._startDragMoveDistance)) {
                break;
            }
            currNode = nodeAndEventType[0];

        } while(false);

        var dragStartEvent = new qc.DragStartEvent(pointer);
        pointer._x = pointer.startX;
        pointer._y = pointer.startY;
        if (currNode) {
            currNode.fireInputEvent('onDragStart', dragStartEvent);
        }
        pointer._x = tmp.x;
        pointer._y = tmp.y;
        event.draggingNode = currNode;
    }
    
    // 还没有拖拽对象
    if (!event.draggingNode &&
        (Math.abs(pointer.distanceX) >= this._startDragMoveDistance ||
        Math.abs(pointer.distanceY) >= this._startDragMoveDistance)) {
        // 从开始位置获取事件响应对象
        var startNode = this._fetchNodeInParent(this.game.world, pointer.startX, pointer.startY);
        var tmp = { x : pointer.x, y : pointer.y };
        // 作为拖拽的接收对象时，不考虑其他事件
        var currNode = startNode ? startNode.getInputEventHandle(this._needHandleDragTypes) : null;
        var dragStartEvent = new qc.DragStartEvent(pointer);
        pointer._x = pointer.startX;
        pointer._y = pointer.startY;
        if (currNode) {
            currNode.fireInputEvent('onDragStart', dragStartEvent);
        }
        pointer._x = tmp.x;
        pointer._y = tmp.y;
        event.draggingNode = currNode;
    }

    // 处理拖拽事件
    if (event.draggingNode) {
        // 作为拖拽的接收对象时，不考虑其他事件
        var currNode = node ? node.getInputEventHandle('onDragDrop') : null;
        var dropEvent = new qc.DropEvent(pointer, event.draggingNode);

        // 接收对象是否运行当前物件拖拽到
        if (currNode && (!currNode.isAllowDrop || currNode.isAllowDrop(event.draggingNode))) {
            currNode.fireInputEvent('onDragOver', dropEvent);
        }

        var dragEvent = new qc.DragEvent(pointer);
        event.draggingNode.fireInputEvent('onDrag', dragEvent);
    }
};

/**
 * 滚轮事件
 * @param deltaX
 * @param deltaY
 */
BaseInputModule.prototype.onWheel = function(deltaX, deltaY) {
    // 仅当有光标存在时处理滚动事件
    if (this.input.hasCursor) {
        var cursorPoint = this.input.cursorPosition;
        var node = this._fetchNodeInParent(this.game.world, cursorPoint.x, cursorPoint.y);
        var eventNode = node ? node.getInputEventHandle('onWheel') : null;
        if (eventNode) {
            var wheelEvent = new qc.WheelEvent(-deltaX, -deltaY);
            eventNode.fireInputEvent('onWheel', wheelEvent);
            // 更新光标的位置
            this.onCursorMove(cursorPoint.x, cursorPoint.y);
        }
    }
};

/**
 * 光标移动
 * @param x
 * @param y
 */
BaseInputModule.prototype.onCursorMove = function(x, y) {
    // 获取当前点击位置下的物件
    // 获取当前点击位置下的物件
    var node = this._fetchNodeInParent(this.game.world, x, y);

    var cursorEvent = new qc.CursorMoveEvent(x, y);

    this.checkEventRecordNode(this);
    this.doPointerExitAndEnter(this, cursorEvent, node);
};

/**
 * 处理节点的移出移入事件
 * @param eventRecord
 * @param event
 * @param enter
 */
BaseInputModule.prototype.doPointerExitAndEnter = function(eventRecord, event, enter) {
    if (eventRecord.overNode === enter) {
        return;
    }
    // 处理离开
    var root = this.findCommonRoot(eventRecord.overNode, enter);
    if (eventRecord.overNode) {
        var tmp = eventRecord.overNode;
        while (tmp && tmp !== this.game.world) {
            if (root && root === tmp)
                break;

            tmp.fireInputEvent('onExit', event);
            tmp = tmp.parent;
        }
    }
    // 处理进入
    if (enter) {
        var tmp = enter;
        while (tmp && tmp !== root && tmp !== this.game.world) {
            tmp.fireInputEvent('onEnter', event);
            tmp = tmp.parent;
        }
    }
    eventRecord.overNode = enter;
};

/**
 * 判定一个点是否在一个扁平化的多边形里
 * @param points {[Number]} - 扁平化的多边形
 * @param x {Number} - x轴位置
 * @param y {Number} - y轴位置
 * @returns {boolean}
 * @private
 */
BaseInputModule.prototype._flattenPolygonContains = function(points, x, y) {
    //  Adapted from http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html by Jonas Raoni Soares Silva
    var length = points.length / 2;
    var inside = false;
    for (var i = -1, j = length - 1; ++i < length; j = i) {
        var ix = points[2 * i];
        var iy = points[2 * i + 1];
        var jx = points[2 * j];
        var jy = points[2 * j + 1];

        if (((iy <= y && y < jy) || (jy <= y && y < iy)) && (x < (jx - ix) * (y - iy) / (jy - iy) + ix))
        {
            inside = !inside;
        }
    }
    return inside;
};

/**
 * 查找两个节点的共同的节点
 * @param one {qc.Node} - 节点
 * @param two {qc.Node} - 节点
 */
BaseInputModule.prototype.findCommonRoot = function(one, two) {
    if (!one || !two) {
        return null;
    }
    var t1 = one;
    var t2 = two;
    while (t1) {
        t2 = two;
        while (t2) {
            if (t1 === t2) {
                return t1;
            }
            t2 = t2.parent;
        }
        t1 = t1.parent;
    }
    return null;
};

/**
 * 获取指定的节点下，位于指定坐标下，显示在最上层的节点
 * @param node {qc.Node} - 指定的父亲节点
 * @param x {number} - 用来选取Node的点的x轴坐标
 * @param y {number} - 用来选取Node的点的y轴坐标
 * @param except {qc.Node} - 需要排除的节点
 * @return {qc.Node} - 节点
 * @private
 */
BaseInputModule.prototype._fetchNodeInParent = function(node, x, y, except) {
    if (!node || !node.phaser.visible)
        return null;
    var localPoint = node.toLocal({x: x, y: y});

    // 如果节点是world需要排除区域外的点
    if (node === this.game.world) {
        if (!node.rect.contains(localPoint.x, localPoint.y))
            return null;
    }

    // 最初的遍历从world开始，故而不用判定node是否为except的子节点
    if (node === except) {
        return null;
    }

    // 判定mask裁切
    if (node.phaser.mask) {
        var len = node.phaser.mask.graphicsData.length;
        var contains = false;
        for (var i = 0; i < node.phaser.mask.graphicsData.length; i++)
        {
            var data = node.phaser.mask.graphicsData[i];

            if (!data.fill)
            {
                continue;
            }

            //  Only deal with fills..
            if (data.shape)
            {
                // 这里的多边形会被扁平化，导致contains无法正确判定
                if (data.shape.points &&
                    data.shape.points.length > 0 &&
                    !(data.shape.points[0] instanceof qc.Point) &&
                    this._flattenPolygonContains(data.shape.points, x, y)) {
                    contains = true;
                }
                else if (data.shape.contains(x, y)) {
                    contains = true;
                }
                break;
            }
        }
        if (len != 0 && !contains) {
            return null;
        }
    }
    if (node.phaser.softClip && node.phaser.softClip.length) {
        if (!qc.GeometricTool.polygonContains(node.phaser.softClip, x, y)) {
            return null;
        }
    }

    var fetchNode = null;

    // 先在子节点中获取
    var children = node.children,
        i = children.length;
    while (i--) {
        fetchNode = this._fetchNodeInParent(children[i], x, y, except);
        if (fetchNode)
            return fetchNode;
    }


    // 判断自己是否满足条件
    if (node.interactive && node.hitArea && node.hitArea.contains && node.hitArea.contains(localPoint.x, localPoint.y)) {
        return node;
    }

    return null;
};
var __extends = function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
var dragonBones;
(function (dragonBones) {
    (function (geom) {
        var Point = (function () {
            function Point(x, y) {
                if (typeof x === "undefined") { x = 0; }
                if (typeof y === "undefined") { y = 0; }
                this.x = x;
                this.y = y;
            }
            Point.prototype.toString = function () {
                return "[Point (x=" + this.x + " y=" + this.y + ")]";
            };
            return Point;
        })();
        geom.Point = Point;

        var Rectangle = (function () {
            function Rectangle(x, y, width, height) {
                if (typeof x === "undefined") { x = 0; }
                if (typeof y === "undefined") { y = 0; }
                if (typeof width === "undefined") { width = 0; }
                if (typeof height === "undefined") { height = 0; }
                this.x = x;
                this.y = y;
                this.width = width;
                this.height = height;
            }
            return Rectangle;
        })();
        geom.Rectangle = Rectangle;

        var Matrix = (function () {
            function Matrix() {
                this.a = 1;
                this.b = 0;
                this.c = 0;
                this.d = 1;
                this.tx = 0;
                this.ty = 0;
            }
            Matrix.prototype.invert = function () {
                var a1 = this.a;
                var b1 = this.b;
                var c1 = this.c;
                var d1 = this.d;
                var tx1 = this.tx;
                var n = a1 * d1 - b1 * c1;

                this.a = d1 / n;
                this.b = -b1 / n;
                this.c = -c1 / n;
                this.d = a1 / n;
                this.tx = (c1 * this.ty - d1 * tx1) / n;
                this.ty = -(a1 * this.ty - b1 * tx1) / n;
            };
            return Matrix;
        })();
        geom.Matrix = Matrix;

        var ColorTransform = (function () {
            function ColorTransform() {
                this.alphaMultiplier = 0;
                this.alphaOffset = 0;
                this.blueMultiplier = 0;
                this.blueOffset = 0;
                this.greenMultiplier = 0;
                this.greenOffset = 0;
                this.redMultiplier = 0;
                this.redOffset = 0;
            }
            return ColorTransform;
        })();
        geom.ColorTransform = ColorTransform;
    })(dragonBones.geom || (dragonBones.geom = {}));
    var geom = dragonBones.geom;

    (function (events) {
        var Event = (function () {
            function Event(type) {
                this.type = type;
            }
            return Event;
        })();
        events.Event = Event;

        var AnimationEvent = (function (_super) {
            __extends(AnimationEvent, _super);
            function AnimationEvent(type) {
                _super.call(this, type);
            }
            AnimationEvent.FADE_IN = "fadeIn";
            AnimationEvent.FADE_OUT = "fadeOut";
            AnimationEvent.START = "start";
            AnimationEvent.COMPLETE = "complete";
            AnimationEvent.LOOP_COMPLETE = "loopComplete";
            AnimationEvent.FADE_IN_COMPLETE = "fadeInComplete";
            AnimationEvent.FADE_OUT_COMPLETE = "fadeOutComplete";
            return AnimationEvent;
        })(Event);
        events.AnimationEvent = AnimationEvent;

        var ArmatureEvent = (function (_super) {
            __extends(ArmatureEvent, _super);
            function ArmatureEvent(type) {
                _super.call(this, type);
            }
            ArmatureEvent.Z_ORDER_UPDATED = "zOrderUpdated";
            return ArmatureEvent;
        })(Event);
        events.ArmatureEvent = ArmatureEvent;

        var FrameEvent = (function (_super) {
            __extends(FrameEvent, _super);
            function FrameEvent(type) {
                _super.call(this, type);
            }
            FrameEvent.ANIMATION_FRAME_EVENT = "animationFrameEvent";
            FrameEvent.BONE_FRAME_EVENT = "boneFrameEvent";
            return FrameEvent;
        })(Event);
        events.FrameEvent = FrameEvent;

        var SoundEvent = (function (_super) {
            __extends(SoundEvent, _super);
            function SoundEvent(type) {
                _super.call(this, type);
            }
            SoundEvent.SOUND = "sound";
            SoundEvent.BONE_FRAME_EVENT = "boneFrameEvent";
            return SoundEvent;
        })(Event);
        events.SoundEvent = SoundEvent;

        var EventDispatcher = (function () {
            function EventDispatcher() {
            }
            EventDispatcher.prototype.hasEventListener = function (type) {
                if (this._listenersMap && this._listenersMap[type]) {
                    return true;
                }
                return false;
            };

            EventDispatcher.prototype.addEventListener = function (type, listener) {
                if (type && listener) {
                    if (!this._listenersMap) {
                        this._listenersMap = {};
                    }
                    var listeners = this._listenersMap[type];
                    if (listeners) {
                        this.removeEventListener(type, listener);
                    }
                    if (listeners) {
                        listeners.push(listener);
                    } else {
                        this._listenersMap[type] = [listener];
                    }
                }
            };

            EventDispatcher.prototype.removeEventListener = function (type, listener) {
                if (!this._listenersMap || !type || !listener) {
                    return;
                }
                var listeners = this._listenersMap[type];
                if (listeners) {
                    var length = listeners.length;
                    for (var i = 0; i < length; i++) {
                        if (listeners[i] == listener) {
                            if (length == 1) {
                                listeners.length = 0;
                                delete this._listenersMap[type];
                            } else {
                                listeners.splice(i, 1);
                            }
                        }
                    }
                }
            };

            EventDispatcher.prototype.removeAllEventListeners = function (type) {
                if (type) {
                    delete this._listenersMap[type];
                } else {
                    this._listenersMap = null;
                }
            };

            EventDispatcher.prototype.dispatchEvent = function (event) {
                if (event) {
                    var listeners = this._listenersMap[event.type];
                    if (listeners) {
                        event.target = this;
                        var listenersCopy = listeners.concat();
                        var length = listeners.length;
                        for (var i = 0; i < length; i++) {
                            listenersCopy[i](event);
                        }
                    }
                }
            };
            return EventDispatcher;
        })();
        events.EventDispatcher = EventDispatcher;

        var SoundEventManager = (function (_super) {
            __extends(SoundEventManager, _super);
            function SoundEventManager() {
                _super.call(this);
                if (SoundEventManager._instance) {
                    throw new Error("Singleton already constructed!");
                }
            }
            SoundEventManager.getInstance = function () {
                if (!SoundEventManager._instance) {
                    SoundEventManager._instance = new SoundEventManager();
                }
                return SoundEventManager._instance;
            };
            return SoundEventManager;
        })(EventDispatcher);
        events.SoundEventManager = SoundEventManager;
    })(dragonBones.events || (dragonBones.events = {}));
    var events = dragonBones.events;

    (function (animation) {
        var TimelineState = (function () {
            function TimelineState() {
                this.transform = new objects.DBTransform();
                this.pivot = new geom.Point();

                this._durationTransform = new objects.DBTransform();
                this._durationPivot = new geom.Point();
                this._durationColor = new geom.ColorTransform();
            }
            TimelineState._borrowObject = function () {
                if (TimelineState._pool.length == 0) {
                    return new TimelineState();
                }
                return TimelineState._pool.pop();
            };

            TimelineState._returnObject = function (timeline) {
                if (TimelineState._pool.indexOf(timeline) < 0) {
                    TimelineState._pool[TimelineState._pool.length] = timeline;
                }

                timeline.clear();
            };

            TimelineState._clear = function () {
                var i = TimelineState._pool.length;
                while (i--) {
                    TimelineState._pool[i].clear();
                }
                TimelineState._pool.length = 0;
            };

            TimelineState.getEaseValue = function (value, easing) {
                if (easing > 1) {
                    var valueEase = 0.5 * (1 - Math.cos(value * Math.PI)) - value;
                    easing -= 1;
                } else if (easing > 0) {
                    valueEase = Math.sin(value * TimelineState.HALF_PI) - value;
                } else if (easing < 0) {
                    valueEase = 1 - Math.cos(value * TimelineState.HALF_PI) - value;
                    easing *= -1;
                }
                return valueEase * easing + value;
            };

            TimelineState.prototype.fadeIn = function (bone, animationState, timeline) {
                this._bone = bone;
                this._animationState = animationState;
                this._timeline = timeline;

                this._originTransform = this._timeline.originTransform;
                this._originPivot = this._timeline.originPivot;

                this._tweenTransform = false;
                this._tweenColor = false;

                this._totalTime = this._animationState.totalTime;

                this.transform.x = 0;
                this.transform.y = 0;
                this.transform.scaleX = 0;
                this.transform.scaleY = 0;
                this.transform.skewX = 0;
                this.transform.skewY = 0;
                this.pivot.x = 0;
                this.pivot.y = 0;

                this._durationTransform.x = 0;
                this._durationTransform.y = 0;
                this._durationTransform.scaleX = 0;
                this._durationTransform.scaleY = 0;
                this._durationTransform.skewX = 0;
                this._durationTransform.skewY = 0;
                this._durationPivot.x = 0;
                this._durationPivot.y = 0;

                this._currentFrame = null;

                switch (this._timeline.getFrameList().length) {
                    case 0:
                        this._bone._arriveAtFrame(null, this, this._animationState, false);
                        this._updateState = 0;
                        break;
                    case 1:
                        this._updateState = -1;
                        break;
                    default:
                        this._updateState = 1;
                        break;
                }
            };

            TimelineState.prototype.fadeOut = function () {
                this.transform.skewX = utils.TransformUtil.formatRadian(this.transform.skewX);
                this.transform.skewY = utils.TransformUtil.formatRadian(this.transform.skewY);
            };

            TimelineState.prototype.update = function (progress) {
                if (this._updateState) {
                    if (this._updateState > 0) {
                        if (this._timeline.scale == 0) {
                            progress = 1;
                        } else {
                            progress /= this._timeline.scale;
                        }

                        if (progress == 1) {
                            progress = 0.99999999;
                        }

                        progress += this._timeline.offset;
                        var loopCount = Math.floor(progress);
                        progress -= loopCount;

                        var playedTime = this._totalTime * progress;
                        var isArrivedFrame = false;
                        var frameIndex;
                        while (!this._currentFrame || playedTime > this._currentFramePosition + this._currentFrameDuration || playedTime < this._currentFramePosition) {
                            if (isArrivedFrame) {
                                this._bone._arriveAtFrame(this._currentFrame, this, this._animationState, true);
                            }
                            isArrivedFrame = true;
                            if (this._currentFrame) {
                                frameIndex = this._timeline.getFrameList().indexOf(this._currentFrame) + 1;
                                if (frameIndex >= this._timeline.getFrameList().length) {
                                    frameIndex = 0;
                                }
                                this._currentFrame = this._timeline.getFrameList()[frameIndex];
                            } else {
                                frameIndex = 0;
                                this._currentFrame = this._timeline.getFrameList()[0];
                            }
                            this._currentFrameDuration = this._currentFrame.duration;
                            this._currentFramePosition = this._currentFrame.position;
                        }

                        if (isArrivedFrame) {
                            this.tweenActive = this._currentFrame.displayIndex >= 0;
                            frameIndex++;
                            if (frameIndex >= this._timeline.getFrameList().length) {
                                frameIndex = 0;
                            }
                            var nextFrame = this._timeline.getFrameList()[frameIndex];

                            if (frameIndex == 0 && this._animationState.loop && this._animationState.loopCount >= Math.abs(this._animationState.loop) - 1 && ((this._currentFramePosition + this._currentFrameDuration) / this._totalTime + loopCount - this._timeline.offset) * this._timeline.scale > 0.99999999) {
                                this._updateState = 0;
                                this._tweenEasing = NaN;
                            } else if (this._currentFrame.displayIndex < 0 || nextFrame.displayIndex < 0 || !this._animationState.tweenEnabled) {
                                this._tweenEasing = NaN;
                            } else if (isNaN(this._animationState.clip.tweenEasing)) {
                                this._tweenEasing = this._currentFrame.tweenEasing;
                            } else {
                                this._tweenEasing = this._animationState.clip.tweenEasing;
                            }

                            if (isNaN(this._tweenEasing)) {
                                this._tweenTransform = false;
                                this._tweenColor = false;
                            } else {
                                this._durationTransform.x = nextFrame.transform.x - this._currentFrame.transform.x;
                                this._durationTransform.y = nextFrame.transform.y - this._currentFrame.transform.y;
                                this._durationTransform.skewX = nextFrame.transform.skewX - this._currentFrame.transform.skewX;
                                this._durationTransform.skewY = nextFrame.transform.skewY - this._currentFrame.transform.skewY;
                                this._durationTransform.scaleX = nextFrame.transform.scaleX - this._currentFrame.transform.scaleX;
                                this._durationTransform.scaleY = nextFrame.transform.scaleY - this._currentFrame.transform.scaleY;

                                if (frameIndex == 0) {
                                    this._durationTransform.skewX = utils.TransformUtil.formatRadian(this._durationTransform.skewX);
                                    this._durationTransform.skewY = utils.TransformUtil.formatRadian(this._durationTransform.skewY);
                                }

                                this._durationPivot.x = nextFrame.pivot.x - this._currentFrame.pivot.x;
                                this._durationPivot.y = nextFrame.pivot.y - this._currentFrame.pivot.y;

                                if (this._durationTransform.x != 0 || this._durationTransform.y != 0 || this._durationTransform.skewX != 0 || this._durationTransform.skewY != 0 || this._durationTransform.scaleX != 0 || this._durationTransform.scaleY != 0 || this._durationPivot.x != 0 || this._durationPivot.y != 0) {
                                    this._tweenTransform = true;
                                } else {
                                    this._tweenTransform = false;
                                }

                                if (this._currentFrame.color && nextFrame.color) {
                                    this._durationColor.alphaOffset = nextFrame.color.alphaOffset - this._currentFrame.color.alphaOffset;
                                    this._durationColor.redOffset = nextFrame.color.redOffset - this._currentFrame.color.redOffset;
                                    this._durationColor.greenOffset = nextFrame.color.greenOffset - this._currentFrame.color.greenOffset;
                                    this._durationColor.blueOffset = nextFrame.color.blueOffset - this._currentFrame.color.blueOffset;

                                    this._durationColor.alphaMultiplier = nextFrame.color.alphaMultiplier - this._currentFrame.color.alphaMultiplier;
                                    this._durationColor.redMultiplier = nextFrame.color.redMultiplier - this._currentFrame.color.redMultiplier;
                                    this._durationColor.greenMultiplier = nextFrame.color.greenMultiplier - this._currentFrame.color.greenMultiplier;
                                    this._durationColor.blueMultiplier = nextFrame.color.blueMultiplier - this._currentFrame.color.blueMultiplier;

                                    if (this._durationColor.alphaOffset != 0 || this._durationColor.redOffset != 0 || this._durationColor.greenOffset != 0 || this._durationColor.blueOffset != 0 || this._durationColor.alphaMultiplier != 0 || this._durationColor.redMultiplier != 0 || this._durationColor.greenMultiplier != 0 || this._durationColor.blueMultiplier != 0) {
                                        this._tweenColor = true;
                                    } else {
                                        this._tweenColor = false;
                                    }
                                } else if (this._currentFrame.color) {
                                    this._tweenColor = true;
                                    this._durationColor.alphaOffset = -this._currentFrame.color.alphaOffset;
                                    this._durationColor.redOffset = -this._currentFrame.color.redOffset;
                                    this._durationColor.greenOffset = -this._currentFrame.color.greenOffset;
                                    this._durationColor.blueOffset = -this._currentFrame.color.blueOffset;

                                    this._durationColor.alphaMultiplier = 1 - this._currentFrame.color.alphaMultiplier;
                                    this._durationColor.redMultiplier = 1 - this._currentFrame.color.redMultiplier;
                                    this._durationColor.greenMultiplier = 1 - this._currentFrame.color.greenMultiplier;
                                    this._durationColor.blueMultiplier = 1 - this._currentFrame.color.blueMultiplier;
                                } else if (nextFrame.color) {
                                    this._tweenColor = true;
                                    this._durationColor.alphaOffset = nextFrame.color.alphaOffset;
                                    this._durationColor.redOffset = nextFrame.color.redOffset;
                                    this._durationColor.greenOffset = nextFrame.color.greenOffset;
                                    this._durationColor.blueOffset = nextFrame.color.blueOffset;

                                    this._durationColor.alphaMultiplier = nextFrame.color.alphaMultiplier - 1;
                                    this._durationColor.redMultiplier = nextFrame.color.redMultiplier - 1;
                                    this._durationColor.greenMultiplier = nextFrame.color.greenMultiplier - 1;
                                    this._durationColor.blueMultiplier = nextFrame.color.blueMultiplier - 1;
                                } else {
                                    this._tweenColor = false;
                                }
                            }

                            if (!this._tweenTransform) {
                                if (this._animationState.blend) {
                                    this.transform.x = this._originTransform.x + this._currentFrame.transform.x;
                                    this.transform.y = this._originTransform.y + this._currentFrame.transform.y;
                                    this.transform.skewX = this._originTransform.skewX + this._currentFrame.transform.skewX;
                                    this.transform.skewY = this._originTransform.skewY + this._currentFrame.transform.skewY;
                                    this.transform.scaleX = this._originTransform.scaleX + this._currentFrame.transform.scaleX;
                                    this.transform.scaleY = this._originTransform.scaleY + this._currentFrame.transform.scaleY;

                                    this.pivot.x = this._originPivot.x + this._currentFrame.pivot.x;
                                    this.pivot.y = this._originPivot.y + this._currentFrame.pivot.y;
                                } else {
                                    this.transform.x = this._currentFrame.transform.x;
                                    this.transform.y = this._currentFrame.transform.y;
                                    this.transform.skewX = this._currentFrame.transform.skewX;
                                    this.transform.skewY = this._currentFrame.transform.skewY;
                                    this.transform.scaleX = this._currentFrame.transform.scaleX;
                                    this.transform.scaleY = this._currentFrame.transform.scaleY;

                                    this.pivot.x = this._currentFrame.pivot.x;
                                    this.pivot.y = this._currentFrame.pivot.y;
                                }
                            }

                            this._bone._arriveAtFrame(this._currentFrame, this, this._animationState, false);
                            if (!this._tweenColor) {
                                if (this._currentFrame.color) {
                                    this._bone._updateColor(this._currentFrame.color.alphaOffset, this._currentFrame.color.redOffset, this._currentFrame.color.greenOffset, this._currentFrame.color.blueOffset, this._currentFrame.color.alphaMultiplier, this._currentFrame.color.redMultiplier, this._currentFrame.color.greenMultiplier, this._currentFrame.color.blueMultiplier, true);
                                } else if (this._bone._isColorChanged) {
                                    this._bone._updateColor(0, 0, 0, 0, 1, 1, 1, 1, false);
                                }
                            }
                        }

                        if (this._tweenTransform || this._tweenColor) {
                            progress = (playedTime - this._currentFramePosition) / this._currentFrameDuration;
                            if (this._tweenEasing) {
                                progress = TimelineState.getEaseValue(progress, this._tweenEasing);
                            }
                        }

                        if (this._tweenTransform) {
                            var currentTransform = this._currentFrame.transform;
                            var currentPivot = this._currentFrame.pivot;
                            if (this._animationState.blend) {
                                this.transform.x = this._originTransform.x + currentTransform.x + this._durationTransform.x * progress;
                                this.transform.y = this._originTransform.y + currentTransform.y + this._durationTransform.y * progress;
                                this.transform.skewX = this._originTransform.skewX + currentTransform.skewX + this._durationTransform.skewX * progress;
                                this.transform.skewY = this._originTransform.skewY + currentTransform.skewY + this._durationTransform.skewY * progress;
                                this.transform.scaleX = this._originTransform.scaleX + currentTransform.scaleX + this._durationTransform.scaleX * progress;
                                this.transform.scaleY = this._originTransform.scaleY + currentTransform.scaleY + this._durationTransform.scaleY * progress;

                                this.pivot.x = this._originPivot.x + currentPivot.x + this._durationPivot.x * progress;
                                this.pivot.y = this._originPivot.y + currentPivot.y + this._durationPivot.y * progress;
                            } else {
                                this.transform.x = currentTransform.x + this._durationTransform.x * progress;
                                this.transform.y = currentTransform.y + this._durationTransform.y * progress;
                                this.transform.skewX = currentTransform.skewX + this._durationTransform.skewX * progress;
                                this.transform.skewY = currentTransform.skewY + this._durationTransform.skewY * progress;
                                this.transform.scaleX = currentTransform.scaleX + this._durationTransform.scaleX * progress;
                                this.transform.scaleY = currentTransform.scaleY + this._durationTransform.scaleY * progress;

                                this.pivot.x = currentPivot.x + this._durationPivot.x * progress;
                                this.pivot.y = currentPivot.y + this._durationPivot.y * progress;
                            }
                        }

                        if (this._tweenColor) {
                            if (this._currentFrame.color) {
                                this._bone._updateColor(this._currentFrame.color.alphaOffset + this._durationColor.alphaOffset * progress, this._currentFrame.color.redOffset + this._durationColor.redOffset * progress, this._currentFrame.color.greenOffset + this._durationColor.greenOffset * progress, this._currentFrame.color.blueOffset + this._durationColor.blueOffset * progress, this._currentFrame.color.alphaMultiplier + this._durationColor.alphaMultiplier * progress, this._currentFrame.color.redMultiplier + this._durationColor.redMultiplier * progress, this._currentFrame.color.greenMultiplier + this._durationColor.greenMultiplier * progress, this._currentFrame.color.blueMultiplier + this._durationColor.blueMultiplier * progress, true);
                            } else {
                                this._bone._updateColor(this._durationColor.alphaOffset * progress, this._durationColor.redOffset * progress, this._durationColor.greenOffset * progress, this._durationColor.blueOffset * progress, 1 + this._durationColor.alphaMultiplier * progress, 1 + this._durationColor.redMultiplier * progress, 1 + this._durationColor.greenMultiplier * progress, 1 + this._durationColor.blueMultiplier * progress, true);
                            }
                        }
                    } else {
                        this._updateState = 0;
                        if (this._animationState.blend) {
                            this.transform.copy(this._originTransform);

                            this.pivot.x = this._originPivot.x;
                            this.pivot.y = this._originPivot.y;
                        } else {
                            this.transform.x = this.transform.y = this.transform.skewX = this.transform.skewY = this.transform.scaleX = this.transform.scaleY = 0;

                            this.pivot.x = 0;
                            this.pivot.y = 0;
                        }

                        this._currentFrame = this._timeline.getFrameList()[0];

                        this.tweenActive = this._currentFrame.displayIndex >= 0;
                        this._bone._arriveAtFrame(this._currentFrame, this, this._animationState, false);

                        if (this._currentFrame.color) {
                            this._bone._updateColor(this._currentFrame.color.alphaOffset, this._currentFrame.color.redOffset, this._currentFrame.color.greenOffset, this._currentFrame.color.blueOffset, this._currentFrame.color.alphaMultiplier, this._currentFrame.color.redMultiplier, this._currentFrame.color.greenMultiplier, this._currentFrame.color.blueMultiplier, true);
                        } else if (this._bone._isColorChanged) {
                            this._bone._updateColor(0, 0, 0, 0, 1, 1, 1, 1, false);
                        }
                    }
                }
            };

            TimelineState.prototype.clear = function () {
                this._updateState = 0;
                this._bone = null;
                this._animationState = null;
                this._timeline = null;
                this._currentFrame = null;
                this._originTransform = null;
                this._originPivot = null;
            };
            TimelineState.HALF_PI = Math.PI * 0.5;

            TimelineState._pool = [];
            return TimelineState;
        })();
        animation.TimelineState = TimelineState;

        var AnimationState = (function () {
            function AnimationState() {
                this.loop = 0;
                this.layer = 0;
                this._timelineStates = {};
            }
            AnimationState._borrowObject = function () {
                if (AnimationState._pool.length == 0) {
                    return new AnimationState();
                }
                return AnimationState._pool.pop();
            };

            AnimationState._returnObject = function (animationState) {
                if (AnimationState._pool.indexOf(animationState) < 0) {
                    AnimationState._pool[AnimationState._pool.length] = animationState;
                }

                animationState.clear();
            };

            AnimationState._clear = function () {
                var i = AnimationState._pool.length;
                while (i--) {
                    AnimationState._pool[i].clear();
                }
                AnimationState._pool.length = 0;
            };

            AnimationState.prototype.fadeIn = function (armature, clip, fadeInTime, timeScale, loop, layer, displayControl, pauseBeforeFadeInComplete) {
                this.layer = layer;
                this.clip = clip;
                this.name = this.clip.name;
                this.totalTime = this.clip.duration;

                this._armature = armature;

                if (Math.round(this.clip.duration * this.clip.frameRate) < 2 || timeScale == Infinity) {
                    this.timeScale = 1;
                    this.currentTime = this.totalTime;
                    if (this.loop >= 0) {
                        this.loop = 1;
                    } else {
                        this.loop = -1;
                    }
                } else {
                    this.timeScale = timeScale;
                    this.currentTime = 0;
                    this.loop = loop;
                }

                this._pauseBeforeFadeInComplete = pauseBeforeFadeInComplete;

                this._fadeInTime = fadeInTime * this.timeScale;
                this._fadeState = 1;
                this._fadeOutBeginTime = 0;
                this._fadeOutWeight = -1;
                this._fadeWeight = 0;
                this._fadeIn = true;
                this._fadeOut = false;

                this.loopCount = -1;
                this.displayControl = displayControl;
                this.isPlaying = true;
                this.isComplete = false;

                this.weight = 1;
                this.blend = true;
                this.enabled = true;
                this.tweenEnabled = true;

                this.updateTimelineStates();
            };

            AnimationState.prototype.fadeOut = function (fadeOutTime, pause) {
                if (typeof pause === "undefined") { pause = false; }
                if (!this._armature || this._fadeOutWeight >= 0) {
                    return;
                }
                this._fadeState = -1;
                this._fadeOutWeight = this._fadeWeight;
                this._fadeOutTime = fadeOutTime * this.timeScale;
                this._fadeOutBeginTime = this.currentTime;
                this._fadeOut = true;

                this.isPlaying = !pause;
                this.displayControl = false;

                for (var index in this._timelineStates) {
                    (this._timelineStates[index]).fadeOut();
                }

                this.enabled = true;
            };

            AnimationState.prototype.play = function () {
                this.isPlaying = true;
            };

            AnimationState.prototype.stop = function () {
                this.isPlaying = false;
            };

            AnimationState.prototype.getMixingTransform = function (timelineName) {
                if (this._mixingTransforms) {
                    return Number(this._mixingTransforms[timelineName]);
                }
                return -1;
            };

            AnimationState.prototype.addMixingTransform = function (timelineName, type, recursive) {
                if (typeof type === "undefined") { type = 2; }
                if (typeof recursive === "undefined") { recursive = true; }
                if (this.clip && this.clip.getTimeline(timelineName)) {
                    if (!this._mixingTransforms) {
                        this._mixingTransforms = {};
                    }
                    if (recursive) {
                        var i = this._armature._boneList.length;
                        var bone;
                        var currentBone;
                        while (i--) {
                            bone = this._armature._boneList[i];
                            if (bone.name == timelineName) {
                                currentBone = bone;
                            }
                            if (currentBone && (currentBone == bone || currentBone.contains(bone))) {
                                this._mixingTransforms[bone.name] = type;
                            }
                        }
                    } else {
                        this._mixingTransforms[timelineName] = type;
                    }

                    this.updateTimelineStates();
                } else {
                    throw new Error();
                }
            };

            AnimationState.prototype.removeMixingTransform = function (timelineName, recursive) {
                if (typeof timelineName === "undefined") { timelineName = null; }
                if (typeof recursive === "undefined") { recursive = true; }
                if (timelineName) {
                    if (recursive) {
                        var i = this._armature._boneList.length;
                        var bone;
                        var currentBone;
                        while (i--) {
                            bone = this._armature._boneList[i];
                            if (bone.name == timelineName) {
                                currentBone = bone;
                            }
                            if (currentBone && (currentBone == bone || currentBone.contains(bone))) {
                                delete this._mixingTransforms[bone.name];
                            }
                        }
                    } else {
                        delete this._mixingTransforms[timelineName];
                    }

                    for (var index in this._mixingTransforms) {
                        var hasMixing = true;
                        break;
                    }
                    if (!hasMixing) {
                        this._mixingTransforms = null;
                    }
                } else {
                    this._mixingTransforms = null;
                }

                this.updateTimelineStates();
            };

            AnimationState.prototype.advanceTime = function (passedTime) {
                if (!this.enabled) {
                    return false;
                }
                var event;
                var isComplete;

                if (this._fadeIn) {
                    this._fadeIn = false;
                    if (this._armature.hasEventListener(events.AnimationEvent.FADE_IN)) {
                        event = new events.AnimationEvent(events.AnimationEvent.FADE_IN);
                        event.animationState = this;
                        this._armature._eventList.push(event);
                    }
                }

                if (this._fadeOut) {
                    this._fadeOut = false;
                    if (this._armature.hasEventListener(events.AnimationEvent.FADE_OUT)) {
                        event = new events.AnimationEvent(events.AnimationEvent.FADE_OUT);
                        event.animationState = this;
                        this._armature._eventList.push(event);
                    }
                }

                this.currentTime += passedTime * this.timeScale;

                if (this.isPlaying && !this.isComplete) {
                    var progress;
                    var currentLoopCount;
                    if (this._pauseBeforeFadeInComplete) {
                        this._pauseBeforeFadeInComplete = false;
                        this.isPlaying = false;
                        progress = 0;
                        currentLoopCount = Math.floor(progress);
                    } else {
                        progress = this.currentTime / this.totalTime;

                        currentLoopCount = Math.floor(progress);
                        if (currentLoopCount != this.loopCount) {
                            if (this.loopCount == -1) {
                                if (this._armature.hasEventListener(events.AnimationEvent.START)) {
                                    event = new events.AnimationEvent(events.AnimationEvent.START);
                                    event.animationState = this;
                                    this._armature._eventList.push(event);
                                }
                            }
                            this.loopCount = currentLoopCount;
                            if (this.loopCount) {
                                if (this.loop && this.loopCount * this.loopCount >= this.loop * this.loop - 1) {
                                    isComplete = true;
                                    progress = 1;
                                    currentLoopCount = 0;
                                    if (this._armature.hasEventListener(events.AnimationEvent.COMPLETE)) {
                                        event = new events.AnimationEvent(events.AnimationEvent.COMPLETE);
                                        event.animationState = this;
                                        this._armature._eventList.push(event);
                                    }
                                } else {
                                    if (this._armature.hasEventListener(events.AnimationEvent.LOOP_COMPLETE)) {
                                        event = new events.AnimationEvent(events.AnimationEvent.LOOP_COMPLETE);
                                        event.animationState = this;
                                        this._armature._eventList.push(event);
                                    }
                                }
                            }
                        }
                    }

                    for (var index in this._timelineStates) {
                        (this._timelineStates[index]).update(progress);
                    }
                    var frameList = this.clip.getFrameList();
                    if (frameList.length > 0) {
                        var playedTime = this.totalTime * (progress - currentLoopCount);
                        var isArrivedFrame = false;
                        var frameIndex;
                        while (!this._currentFrame || playedTime > this._currentFrame.position + this._currentFrame.duration || playedTime < this._currentFrame.position) {
                            if (isArrivedFrame) {
                                this._armature._arriveAtFrame(this._currentFrame, null, this, true);
                            }
                            isArrivedFrame = true;
                            if (this._currentFrame) {
                                frameIndex = frameList.indexOf(this._currentFrame);
                                frameIndex++;
                                if (frameIndex >= frameList.length) {
                                    frameIndex = 0;
                                }
                                this._currentFrame = frameList[frameIndex];
                            } else {
                                this._currentFrame = frameList[0];
                            }
                        }

                        if (isArrivedFrame) {
                            this._armature._arriveAtFrame(this._currentFrame, null, this, false);
                        }
                    }
                }

                if (this._fadeState > 0) {
                    if (this._fadeInTime == 0) {
                        this._fadeWeight = 1;
                        this._fadeState = 0;
                        this.isPlaying = true;
                        if (this._armature.hasEventListener(events.AnimationEvent.FADE_IN_COMPLETE)) {
                            event = new events.AnimationEvent(events.AnimationEvent.FADE_IN_COMPLETE);
                            event.animationState = this;
                            this._armature._eventList.push(event);
                        }
                    } else {
                        this._fadeWeight = this.currentTime / this._fadeInTime;
                        if (this._fadeWeight >= 1) {
                            this._fadeWeight = 1;
                            this._fadeState = 0;
                            if (!this.isPlaying) {
                                this.currentTime -= this._fadeInTime;
                            }
                            this.isPlaying = true;
                            if (this._armature.hasEventListener(events.AnimationEvent.FADE_IN_COMPLETE)) {
                                event = new events.AnimationEvent(events.AnimationEvent.FADE_IN_COMPLETE);
                                event.animationState = this;
                                this._armature._eventList.push(event);
                            }
                        }
                    }
                } else if (this._fadeState < 0) {
                    if (this._fadeOutTime == 0) {
                        this._fadeWeight = 0;
                        this._fadeState = 0;
                        if (this._armature.hasEventListener(events.AnimationEvent.FADE_OUT_COMPLETE)) {
                            event = new events.AnimationEvent(events.AnimationEvent.FADE_OUT_COMPLETE);
                            event.animationState = this;
                            this._armature._eventList.push(event);
                        }
                        return true;
                    } else {
                        this._fadeWeight = (1 - (this.currentTime - this._fadeOutBeginTime) / this._fadeOutTime) * this._fadeOutWeight;
                        if (this._fadeWeight <= 0) {
                            this._fadeWeight = 0;
                            this._fadeState = 0;
                            if (this._armature.hasEventListener(events.AnimationEvent.FADE_OUT_COMPLETE)) {
                                event = new events.AnimationEvent(events.AnimationEvent.FADE_OUT_COMPLETE);
                                event.animationState = this;
                                this._armature._eventList.push(event);
                            }
                            return true;
                        }
                    }
                }

                if (isComplete) {
                    this.isComplete = true;
                    if (this.loop < 0) {
                        this.fadeOut((this._fadeOutWeight || this._fadeInTime) / this.timeScale, true);
                    }
                }

                return false;
            };

            AnimationState.prototype.updateTimelineStates = function () {
                if (this._mixingTransforms) {
                    for (var timelineName in this._timelineStates) {
                        if (this._mixingTransforms[timelineName] == null) {
                            this.removeTimelineState(timelineName);
                        }
                    }

                    for (timelineName in this._mixingTransforms) {
                        if (!this._timelineStates[timelineName]) {
                            this.addTimelineState(timelineName);
                        }
                    }
                } else {
                    for (timelineName in this.clip.getTimelines()) {
                        if (!this._timelineStates[timelineName]) {
                            this.addTimelineState(timelineName);
                        }
                    }
                }
            };

            AnimationState.prototype.addTimelineState = function (timelineName) {
                var bone = this._armature.getBone(timelineName);
                if (bone) {
                    var timelineState = TimelineState._borrowObject();
                    var timeline = this.clip.getTimeline(timelineName);
                    timelineState.fadeIn(bone, this, timeline);
                    this._timelineStates[timelineName] = timelineState;
                }
            };

            AnimationState.prototype.removeTimelineState = function (timelineName) {
                TimelineState._returnObject(this._timelineStates[timelineName]);
                delete this._timelineStates[timelineName];
            };

            AnimationState.prototype.clear = function () {
                this.clip = null;
                this.enabled = false;

                this._armature = null;
                this._currentFrame = null;
                this._mixingTransforms = null;

                for (var timelineName in this._timelineStates) {
                    this.removeTimelineState(timelineName);
                }
            };
            AnimationState._pool = [];
            return AnimationState;
        })();
        animation.AnimationState = AnimationState;

        var Animation = (function () {
            function Animation(armature) {
                this._armature = armature;
                this._animationLayer = [];
                this._isPlaying = false;

                this.animationNameList = [];
                this.tweenEnabled = true;
                this.timeScale = 1;
            }
            Animation.prototype.getLastAnimationName = function () {
                return this._lastAnimationState ? this._lastAnimationState.name : null;
            };

            Animation.prototype.getLastAnimationState = function () {
                return this._lastAnimationState;
            };

            Animation.prototype.getAnimationDataList = function () {
                return this._animationDataList;
            };
            Animation.prototype.setAnimationDataList = function (value) {
                this._animationDataList = value;
                this.animationNameList.length = 0;
                for (var index in this._animationDataList) {
                    this.animationNameList[this.animationNameList.length] = this._animationDataList[index].name;
                }
            };

            Animation.prototype.getIsPlaying = function () {
                return this._isPlaying && !this.getIsComplete();
            };

            Animation.prototype.getIsComplete = function () {
                if (this._lastAnimationState) {
                    if (!this._lastAnimationState.isComplete) {
                        return false;
                    }
                    var j = this._animationLayer.length;
                    while (j--) {
                        var animationStateList = this._animationLayer[j];
                        var i = animationStateList.length;
                        while (i--) {
                            if (!animationStateList[i].isComplete) {
                                return false;
                            }
                        }
                    }
                    return true;
                }
                return false;
            };

            Animation.prototype.dispose = function () {
                if (!this._armature) {
                    return;
                }
                this.stop();
                var i = this._animationLayer.length;
                while (i--) {
                    var animationStateList = this._animationLayer[i];
                    var j = animationStateList.length;
                    while (j--) {
                        AnimationState._returnObject(animationStateList[j]);
                    }
                    animationStateList.length = 0;
                }
                this._animationLayer.length = 0;
                this.animationNameList.length = 0;

                this._armature = null;
                this._animationLayer = null;
                this._animationDataList = null;
                this.animationNameList = null;
            };

            Animation.prototype.gotoAndPlay = function (animationName, fadeInTime, duration, loop, layer, group, fadeOutMode, displayControl, pauseFadeOut, pauseFadeIn) {
                if (typeof fadeInTime === "undefined") { fadeInTime = -1; }
                if (typeof duration === "undefined") { duration = -1; }
                if (typeof loop === "undefined") { loop = NaN; }
                if (typeof layer === "undefined") { layer = 0; }
                if (typeof group === "undefined") { group = null; }
                if (typeof fadeOutMode === "undefined") { fadeOutMode = Animation.SAME_LAYER_AND_GROUP; }
                if (typeof displayControl === "undefined") { displayControl = true; }
                if (typeof pauseFadeOut === "undefined") { pauseFadeOut = true; }
                if (typeof pauseFadeIn === "undefined") { pauseFadeIn = true; }
                if (!this._animationDataList) {
                    return null;
                }
                var i = this._animationDataList.length;
                var animationData;
                while (i--) {
                    if (this._animationDataList[i].name == animationName) {
                        animationData = this._animationDataList[i];
                        break;
                    }
                }
                if (!animationData) {
                    return null;
                }

                this._isPlaying = true;

                fadeInTime = fadeInTime < 0 ? (animationData.fadeInTime < 0 ? 0.3 : animationData.fadeInTime) : fadeInTime;

                var durationScale;
                if (duration < 0) {
                    durationScale = animationData.scale < 0 ? 1 : animationData.scale;
                } else {
                    durationScale = duration / animationData.duration;
                }

                loop = isNaN(loop) ? animationData.loop : loop;
                layer = this.addLayer(layer);

                var animationState;
                var animationStateList;
                switch (fadeOutMode) {
                    case Animation.NONE:
                        break;
                    case Animation.SAME_LAYER:
                        animationStateList = this._animationLayer[layer];
                        i = animationStateList.length;
                        while (i--) {
                            animationState = animationStateList[i];
                            animationState.fadeOut(fadeInTime, pauseFadeOut);
                        }
                        break;
                    case Animation.SAME_GROUP:
                        j = this._animationLayer.length;
                        while (j--) {
                            animationStateList = this._animationLayer[j];
                            i = animationStateList.length;
                            while (i--) {
                                animationState = animationStateList[i];
                                if (animationState.group == group) {
                                    animationState.fadeOut(fadeInTime, pauseFadeOut);
                                }
                            }
                        }
                        break;
                    case Animation.ALL:
                        var j = this._animationLayer.length;
                        while (j--) {
                            animationStateList = this._animationLayer[j];
                            i = animationStateList.length;
                            while (i--) {
                                animationState = animationStateList[i];
                                animationState.fadeOut(fadeInTime, pauseFadeOut);
                            }
                        }
                        break;
                    case Animation.SAME_LAYER_AND_GROUP:
                    default:
                        animationStateList = this._animationLayer[layer];
                        i = animationStateList.length;
                        while (i--) {
                            animationState = animationStateList[i];
                            if (animationState.group == group) {
                                animationState.fadeOut(fadeInTime, pauseFadeOut);
                            }
                        }
                        break;
                }

                this._lastAnimationState = AnimationState._borrowObject();
                this._lastAnimationState.group = group;
                this._lastAnimationState.tweenEnabled = this.tweenEnabled;
                this._lastAnimationState.fadeIn(this._armature, animationData, fadeInTime, 1 / durationScale, loop, layer, displayControl, pauseFadeIn);

                this.addState(this._lastAnimationState);

                var slotList = this._armature._slotList;
                var slot;
                var childArmature;
                i = slotList.length;
                while (i--) {
                    slot = slotList[i];
                    childArmature = slot.getChildArmature();
                    if (childArmature) {
                        childArmature.animation.gotoAndPlay(animationName, fadeInTime);
                    }
                }

                return this._lastAnimationState;
            };

            Animation.prototype.play = function () {
                if (!this._animationDataList || this._animationDataList.length == 0) {
                    return;
                }
                if (!this._lastAnimationState) {
                    this.gotoAndPlay(this._animationDataList[0].name);
                } else if (!this._isPlaying) {
                    this._isPlaying = true;
                } else {
                    this.gotoAndPlay(this._lastAnimationState.name);
                }
            };

            Animation.prototype.stop = function () {
                this._isPlaying = false;
            };

            Animation.prototype.getState = function (name, layer) {
                if (typeof layer === "undefined") { layer = 0; }
                var l = this._animationLayer.length;
                if (l == 0) {
                    return null;
                } else if (layer >= l) {
                    layer = l - 1;
                }

                var animationStateList = this._animationLayer[layer];
                if (!animationStateList) {
                    return null;
                }
                var i = animationStateList.length;
                while (i--) {
                    if (animationStateList[i].name == name) {
                        return animationStateList[i];
                    }
                }

                return null;
            };

            Animation.prototype.hasAnimation = function (animationName) {
                var i = this._animationDataList.length;
                while (i--) {
                    if (this._animationDataList[i].name == animationName) {
                        return true;
                    }
                }

                return false;
            };

            Animation.prototype.advanceTime = function (passedTime) {
                if (!this._isPlaying) {
                    return;
                }
                passedTime *= this.timeScale;

                var l = this._armature._boneList.length;
                var i;
                var j;
                var k = l;
                var stateListLength;
                var bone;
                var boneName;
                var weigthLeft;

                var x;
                var y;
                var skewX;
                var skewY;
                var scaleX;
                var scaleY;
                var pivotX;
                var pivotY;

                var layerTotalWeight;
                var animationStateList;
                var animationState;
                var timelineState;
                var weight;
                var transform;
                var pivot;

                l--;
                while (k--) {
                    bone = this._armature._boneList[k];
                    boneName = bone.name;
                    weigthLeft = 1;

                    x = 0;
                    y = 0;
                    skewX = 0;
                    skewY = 0;
                    scaleX = 0;
                    scaleY = 0;
                    pivotX = 0;
                    pivotY = 0;

                    i = this._animationLayer.length;
                    while (i--) {
                        layerTotalWeight = 0;
                        animationStateList = this._animationLayer[i];
                        stateListLength = animationStateList.length;
                        for (j = 0; j < stateListLength; j++) {
                            animationState = animationStateList[j];
                            if (k == l) {
                                if (animationState.advanceTime(passedTime)) {
                                    this.removeState(animationState);
                                    j--;
                                    stateListLength--;
                                    continue;
                                }
                            }

                            timelineState = animationState._timelineStates[boneName];
                            if (timelineState && timelineState.tweenActive) {
                                weight = animationState._fadeWeight * animationState.weight * weigthLeft;
                                transform = timelineState.transform;
                                pivot = timelineState.pivot;
                                x += transform.x * weight;
                                y += transform.y * weight;
                                skewX += transform.skewX * weight;
                                skewY += transform.skewY * weight;
                                scaleX += transform.scaleX * weight;
                                scaleY += transform.scaleY * weight;
                                pivotX += pivot.x * weight;
                                pivotY += pivot.y * weight;

                                layerTotalWeight += weight;
                            }
                        }

                        if (layerTotalWeight >= weigthLeft) {
                            break;
                        } else {
                            weigthLeft -= layerTotalWeight;
                        }
                    }
                    transform = bone.tween;
                    pivot = bone._tweenPivot;

                    transform.x = x;
                    transform.y = y;
                    transform.skewX = skewX;
                    transform.skewY = skewY;
                    transform.scaleX = scaleX;
                    transform.scaleY = scaleY;
                    pivot.x = pivotX;
                    pivot.y = pivotY;
                }
            };

            Animation.prototype.addLayer = function (layer) {
                if (layer >= this._animationLayer.length) {
                    layer = this._animationLayer.length;
                    this._animationLayer[layer] = [];
                }
                return layer;
            };

            Animation.prototype.addState = function (animationState) {
                var animationStateList = this._animationLayer[animationState.layer];
                animationStateList.push(animationState);
            };

            Animation.prototype.removeState = function (animationState) {
                var layer = animationState.layer;
                var animationStateList = this._animationLayer[layer];
                animationStateList.splice(animationStateList.indexOf(animationState), 1);

                AnimationState._returnObject(animationState);

                if (animationStateList.length == 0 && layer == this._animationLayer.length - 1) {
                    this._animationLayer.length--;
                }
            };
            Animation.NONE = "none";
            Animation.SAME_LAYER = "sameLayer";
            Animation.SAME_GROUP = "sameGroup";
            Animation.SAME_LAYER_AND_GROUP = "sameLayerAndGroup";
            Animation.ALL = "all";
            return Animation;
        })();
        animation.Animation = Animation;
    })(dragonBones.animation || (dragonBones.animation = {}));
    var animation = dragonBones.animation;

    (function (objects) {
        var DBTransform = (function () {
            function DBTransform() {
                this.x = 0;
                this.y = 0;
                this.skewX = 0;
                this.skewY = 0;
                this.scaleX = 1;
                this.scaleY = 1;
            }
            DBTransform.prototype.getRotation = function () {
                return this.skewX;
            };
            DBTransform.prototype.setRotation = function (value) {
                this.skewX = this.skewY = value;
            };

            DBTransform.prototype.copy = function (transform) {
                this.x = transform.x;
                this.y = transform.y;
                this.skewX = transform.skewX;
                this.skewY = transform.skewY;
                this.scaleX = transform.scaleX;
                this.scaleY = transform.scaleY;
            };

            DBTransform.prototype.toString = function () {
                return "[DBTransform (x=" + this.x + " y=" + this.y + " skewX=" + this.skewX + " skewY=" + this.skewY + " scaleX=" + this.scaleX + " scaleY=" + this.scaleY + ")]";
            };
            return DBTransform;
        })();
        objects.DBTransform = DBTransform;

        var Frame = (function () {
            function Frame() {
                this.position = 0;
                this.duration = 0;
            }
            Frame.prototype.dispose = function () {
            };
            return Frame;
        })();
        objects.Frame = Frame;

        var TransformFrame = (function (_super) {
            __extends(TransformFrame, _super);
            function TransformFrame() {
                _super.call(this);

                this.tweenEasing = 0;
                this.tweenRotate = 0;
                this.displayIndex = 0;
                this.zOrder = NaN;
                this.visible = true;

                this.global = new DBTransform();
                this.transform = new DBTransform();
                this.pivot = new geom.Point();
            }
            TransformFrame.prototype.dispose = function () {
                _super.prototype.dispose.call(this);
                this.global = null;
                this.transform = null;

                this.pivot = null;
                this.color = null;
            };
            return TransformFrame;
        })(Frame);
        objects.TransformFrame = TransformFrame;

        var Timeline = (function () {
            function Timeline() {
                this._frameList = [];
                this.duration = 0;
                this.scale = 1;
            }
            Timeline.prototype.getFrameList = function () {
                return this._frameList;
            };

            Timeline.prototype.dispose = function () {
                var i = this._frameList.length;
                while (i--) {
                    this._frameList[i].dispose();
                }
                this._frameList.length = 0;
                this._frameList = null;
            };

            Timeline.prototype.addFrame = function (frame) {
                if (!frame) {
                    throw new Error();
                }

                if (this._frameList.indexOf(frame) < 0) {
                    this._frameList[this._frameList.length] = frame;
                } else {
                    throw new Error();
                }
            };
            return Timeline;
        })();
        objects.Timeline = Timeline;

        var TransformTimeline = (function (_super) {
            __extends(TransformTimeline, _super);
            function TransformTimeline() {
                _super.call(this);
                this.originTransform = new DBTransform();
                this.originPivot = new geom.Point();
                this.offset = 0;
                this.transformed = false;
            }
            TransformTimeline.prototype.dispose = function () {
                if (this == TransformTimeline.HIDE_TIMELINE) {
                    return;
                }
                _super.prototype.dispose.call(this);
                this.originTransform = null;
                this.originPivot = null;
            };
            TransformTimeline.HIDE_TIMELINE = new TransformTimeline();
            return TransformTimeline;
        })(Timeline);
        objects.TransformTimeline = TransformTimeline;

        var AnimationData = (function (_super) {
            __extends(AnimationData, _super);
            function AnimationData() {
                _super.call(this);
                this.frameRate = 0;
                this.loop = 0;
                this.tweenEasing = NaN;
                this.fadeInTime = 0;

                this._timelines = {};
            }
            AnimationData.prototype.getTimelines = function () {
                return this._timelines;
            };

            AnimationData.prototype.dispose = function () {
                _super.prototype.dispose.call(this);

                for (var timelineName in this._timelines) {
                    (this._timelines[timelineName]).dispose();
                }
                this._timelines = null;
            };

            AnimationData.prototype.getTimeline = function (timelineName) {
                return this._timelines[timelineName];
            };

            AnimationData.prototype.addTimeline = function (timeline, timelineName) {
                if (!timeline) {
                    throw new Error();
                }

                this._timelines[timelineName] = timeline;
            };
            return AnimationData;
        })(Timeline);
        objects.AnimationData = AnimationData;

        var DisplayData = (function () {
            function DisplayData() {
                this.transform = new DBTransform();
            }
            DisplayData.prototype.dispose = function () {
                this.transform = null;
                this.pivot = null;
            };
            DisplayData.ARMATURE = "armature";
            DisplayData.IMAGE = "image";
            return DisplayData;
        })();
        objects.DisplayData = DisplayData;

        var SlotData = (function () {
            function SlotData() {
                this._displayDataList = [];
                this.zOrder = 0;
            }
            SlotData.prototype.getDisplayDataList = function () {
                return this._displayDataList;
            };

            SlotData.prototype.dispose = function () {
                var i = this._displayDataList.length;
                while (i--) {
                    this._displayDataList[i].dispose();
                }
                this._displayDataList.length = 0;
                this._displayDataList = null;
            };

            SlotData.prototype.addDisplayData = function (displayData) {
                if (!displayData) {
                    throw new Error();
                }
                if (this._displayDataList.indexOf(displayData) < 0) {
                    this._displayDataList[this._displayDataList.length] = displayData;
                } else {
                    throw new Error();
                }
            };

            SlotData.prototype.getDisplayData = function (displayName) {
                var i = this._displayDataList.length;
                while (i--) {
                    if (this._displayDataList[i].name == displayName) {
                        return this._displayDataList[i];
                    }
                }

                return null;
            };
            return SlotData;
        })();
        objects.SlotData = SlotData;

        var BoneData = (function () {
            function BoneData() {
                this.length = 0;
                this.global = new DBTransform();
                this.transform = new DBTransform();
            }
            BoneData.prototype.dispose = function () {
                this.global = null;
                this.transform = null;
            };
            return BoneData;
        })();
        objects.BoneData = BoneData;

        var SkinData = (function () {
            function SkinData() {
                this._slotDataList = [];
            }
            SkinData.prototype.getSlotDataList = function () {
                return this._slotDataList;
            };

            SkinData.prototype.dispose = function () {
                var i = this._slotDataList.length;
                while (i--) {
                    this._slotDataList[i].dispose();
                }
                this._slotDataList.length = 0;
                this._slotDataList = null;
            };

            SkinData.prototype.getSlotData = function (slotName) {
                var i = this._slotDataList.length;
                while (i--) {
                    if (this._slotDataList[i].name == slotName) {
                        return this._slotDataList[i];
                    }
                }
                return null;
            };

            SkinData.prototype.addSlotData = function (slotData) {
                if (!slotData) {
                    throw new Error();
                }

                if (this._slotDataList.indexOf(slotData) < 0) {
                    this._slotDataList[this._slotDataList.length] = slotData;
                } else {
                    throw new Error();
                }
            };
            return SkinData;
        })();
        objects.SkinData = SkinData;

        var ArmatureData = (function () {
            function ArmatureData() {
                this._boneDataList = [];
                this._skinDataList = [];
                this._animationDataList = [];
            }
            ArmatureData.prototype.getBoneDataList = function () {
                return this._boneDataList;
            };

            ArmatureData.prototype.getSkinDataList = function () {
                return this._skinDataList;
            };

            ArmatureData.prototype.getAnimationDataList = function () {
                return this._animationDataList;
            };

            ArmatureData.prototype.dispose = function () {
                var i = this._boneDataList.length;
                while (i--) {
                    this._boneDataList[i].dispose();
                }
                i = this._skinDataList.length;
                while (i--) {
                    this._skinDataList[i].dispose();
                }
                i = this._animationDataList.length;
                while (i--) {
                    this._animationDataList[i].dispose();
                }
                this._boneDataList.length = 0;
                this._skinDataList.length = 0;
                this._animationDataList.length = 0;
                this._boneDataList = null;
                this._skinDataList = null;
                this._animationDataList = null;
            };

            ArmatureData.prototype.getBoneData = function (boneName) {
                var i = this._boneDataList.length;
                while (i--) {
                    if (this._boneDataList[i].name == boneName) {
                        return this._boneDataList[i];
                    }
                }
                return null;
            };

            ArmatureData.prototype.getSkinData = function (skinName) {
                if (!skinName) {
                    return this._skinDataList[0];
                }
                var i = this._skinDataList.length;
                while (i--) {
                    if (this._skinDataList[i].name == skinName) {
                        return this._skinDataList[i];
                    }
                }

                return null;
            };

            ArmatureData.prototype.getAnimationData = function (animationName) {
                var i = this._animationDataList.length;
                while (i--) {
                    if (this._animationDataList[i].name == animationName) {
                        return this._animationDataList[i];
                    }
                }
                return null;
            };

            ArmatureData.prototype.addBoneData = function (boneData) {
                if (!boneData) {
                    throw new Error();
                }

                if (this._boneDataList.indexOf(boneData) < 0) {
                    this._boneDataList[this._boneDataList.length] = boneData;
                } else {
                    throw new Error();
                }
            };

            ArmatureData.prototype.addSkinData = function (skinData) {
                if (!skinData) {
                    throw new Error();
                }

                if (this._skinDataList.indexOf(skinData) < 0) {
                    this._skinDataList[this._skinDataList.length] = skinData;
                } else {
                    throw new Error();
                }
            };

            ArmatureData.prototype.addAnimationData = function (animationData) {
                if (!animationData) {
                    throw new Error();
                }

                if (this._animationDataList.indexOf(animationData) < 0) {
                    this._animationDataList[this._animationDataList.length] = animationData;
                }
            };

            ArmatureData.prototype.sortBoneDataList = function () {
                var i = this._boneDataList.length;
                if (i == 0) {
                    return;
                }

                var helpArray = [];
                while (i--) {
                    var boneData = this._boneDataList[i];
                    var level = 0;
                    var parentData = boneData;
                    while (parentData && parentData.parent) {
                        level++;
                        parentData = this.getBoneData(parentData.parent);
                    }
                    helpArray[i] = { level: level, boneData: boneData };
                }

                helpArray.sort(this.sortBoneData);

                i = helpArray.length;
                while (i--) {
                    this._boneDataList[i] = helpArray[i].boneData;
                }
            };

            ArmatureData.prototype.sortBoneData = function (object1, object2) {
                return object1.level > object2.level ? 1 : -1;
            };
            return ArmatureData;
        })();
        objects.ArmatureData = ArmatureData;

        var SkeletonData = (function () {
            function SkeletonData() {
                this._armatureDataList = [];
                this._subTexturePivots = {};
            }
            SkeletonData.prototype.getArmatureNames = function () {
                var nameList = [];
                for (var armatureDataIndex in this._armatureDataList) {
                    nameList[nameList.length] = this._armatureDataList[armatureDataIndex].name;
                }
                return nameList;
            };

            SkeletonData.prototype.getArmatureDataList = function () {
                return this._armatureDataList;
            };

            SkeletonData.prototype.dispose = function () {
                for (var armatureDataIndex in this._armatureDataList) {
                    this._armatureDataList[armatureDataIndex].dispose();
                }
                this._armatureDataList.length = 0;

                this._armatureDataList = null;
                this._subTexturePivots = null;
            };

            SkeletonData.prototype.getArmatureData = function (armatureName) {
                var i = this._armatureDataList.length;
                while (i--) {
                    if (this._armatureDataList[i].name == armatureName) {
                        return this._armatureDataList[i];
                    }
                }

                return null;
            };

            SkeletonData.prototype.addArmatureData = function (armatureData) {
                if (!armatureData) {
                    throw new Error();
                }

                if (this._armatureDataList.indexOf(armatureData) < 0) {
                    this._armatureDataList[this._armatureDataList.length] = armatureData;
                } else {
                    throw new Error();
                }
            };

            SkeletonData.prototype.removeArmatureData = function (armatureData) {
                var index = this._armatureDataList.indexOf(armatureData);
                if (index >= 0) {
                    this._armatureDataList.splice(index, 1);
                }
            };

            SkeletonData.prototype.removeArmatureDataByName = function (armatureName) {
                var i = this._armatureDataList.length;
                while (i--) {
                    if (this._armatureDataList[i].name == armatureName) {
                        this._armatureDataList.splice(i, 1);
                    }
                }
            };

            SkeletonData.prototype.getSubTexturePivot = function (subTextureName) {
                return this._subTexturePivots[subTextureName];
            };

            SkeletonData.prototype.addSubTexturePivot = function (x, y, subTextureName) {
                var point = this._subTexturePivots[subTextureName];
                if (point) {
                    point.x = x;
                    point.y = y;
                } else {
                    this._subTexturePivots[subTextureName] = point = new geom.Point(x, y);
                }

                return point;
            };

            SkeletonData.prototype.removeSubTexturePivot = function (subTextureName) {
                if (subTextureName) {
                    delete this._subTexturePivots[subTextureName];
                } else {
                    for (subTextureName in this._subTexturePivots) {
                        delete this._subTexturePivots[subTextureName];
                    }
                }
            };
            return SkeletonData;
        })();
        objects.SkeletonData = SkeletonData;

        var DataParser = (function () {
            function DataParser() {
            }
            DataParser.parseTextureAtlasData = function (rawData, scale) {
                if (typeof scale === "undefined") { scale = 1; }
                if (!rawData) {
                    throw new Error();
                }

                var textureAtlasData = {};
                textureAtlasData.__name = rawData[utils.ConstValues.A_NAME];
                var subTextureList = rawData[utils.ConstValues.SUB_TEXTURE];
                for (var index in subTextureList) {
                    var subTextureObject = subTextureList[index];
                    var subTextureName = subTextureObject[utils.ConstValues.A_NAME];
                    var subTextureData = new geom.Rectangle(Number(subTextureObject[utils.ConstValues.A_X]) / scale, Number(subTextureObject[utils.ConstValues.A_Y]) / scale, Number(subTextureObject[utils.ConstValues.A_WIDTH]) / scale, Number(subTextureObject[utils.ConstValues.A_HEIGHT]) / scale);
                    textureAtlasData[subTextureName] = subTextureData;
                }

                return textureAtlasData;
            };

            DataParser.parseSkeletonData = function (rawData) {
                if (!rawData) {
                    throw new Error();
                }

                var frameRate = Number(rawData[utils.ConstValues.A_FRAME_RATE]);
                var data = new SkeletonData();
                data.name = rawData[utils.ConstValues.A_NAME];

                var armatureObjectList = rawData[utils.ConstValues.ARMATURE];
                for (var index in armatureObjectList) {
                    var armatureObject = armatureObjectList[index];
                    data.addArmatureData(DataParser.parseArmatureData(armatureObject, data, frameRate));
                }

                return data;
            };

            DataParser.parseArmatureData = function (armatureObject, data, frameRate) {
                var armatureData = new ArmatureData();
                armatureData.name = armatureObject[utils.ConstValues.A_NAME];

                var boneObjectList = armatureObject[utils.ConstValues.BONE];
                for (var index in boneObjectList) {
                    var boneObject = boneObjectList[index];
                    armatureData.addBoneData(DataParser.parseBoneData(boneObject));
                }

                var skinObjectList = armatureObject[utils.ConstValues.SKIN];
                for (var index in skinObjectList) {
                    var skinObject = skinObjectList[index];
                    armatureData.addSkinData(DataParser.parseSkinData(skinObject, data));
                }

                utils.DBDataUtil.transformArmatureData(armatureData);
                armatureData.sortBoneDataList();

                var animationObjectList = armatureObject[utils.ConstValues.ANIMATION];

                for (var index in animationObjectList) {
                    var animationObject = animationObjectList[index];
                    armatureData.addAnimationData(DataParser.parseAnimationData(animationObject, armatureData, frameRate));
                }

                return armatureData;
            };

            DataParser.parseBoneData = function (boneObject) {
                var boneData = new BoneData();
                boneData.name = boneObject[utils.ConstValues.A_NAME];
                boneData.parent = boneObject[utils.ConstValues.A_PARENT];
                boneData.length = Number(boneObject[utils.ConstValues.A_LENGTH]) || 0;

                DataParser.parseTransform(boneObject[utils.ConstValues.TRANSFORM], boneData.global);
                boneData.transform.copy(boneData.global);

                return boneData;
            };

            DataParser.parseSkinData = function (skinObject, data) {
                var skinData = new SkinData();
                skinData.name = skinObject[utils.ConstValues.A_NAME];
                var slotObjectList = skinObject[utils.ConstValues.SLOT];
                for (var index in slotObjectList) {
                    var slotObject = slotObjectList[index];
                    skinData.addSlotData(DataParser.parseSlotData(slotObject, data));
                }

                return skinData;
            };

            DataParser.parseSlotData = function (slotObject, data) {
                var slotData = new SlotData();
                slotData.name = slotObject[utils.ConstValues.A_NAME];
                slotData.parent = slotObject[utils.ConstValues.A_PARENT];
                slotData.zOrder = Number(slotObject[utils.ConstValues.A_Z_ORDER]);

                var displayObjectList = slotObject[utils.ConstValues.DISPLAY];
                for (var index in displayObjectList) {
                    var displayObject = displayObjectList[index];
                    slotData.addDisplayData(DataParser.parseDisplayData(displayObject, data));
                }

                return slotData;
            };

            DataParser.parseDisplayData = function (displayObject, data) {
                var displayData = new DisplayData();
                displayData.name = displayObject[utils.ConstValues.A_NAME];
                displayData.type = displayObject[utils.ConstValues.A_TYPE];

                displayData.pivot = data.addSubTexturePivot(0, 0, displayData.name);

                DataParser.parseTransform(displayObject[utils.ConstValues.TRANSFORM], displayData.transform, displayData.pivot);

                return displayData;
            };

            DataParser.parseAnimationData = function (animationObject, armatureData, frameRate) {
                var animationData = new AnimationData();
                animationData.name = animationObject[utils.ConstValues.A_NAME];
                animationData.frameRate = frameRate;
                animationData.loop = Number(animationObject[utils.ConstValues.A_LOOP]) || 0;
                animationData.fadeInTime = Number(animationObject[utils.ConstValues.A_FADE_IN_TIME]);
                animationData.duration = Number(animationObject[utils.ConstValues.A_DURATION]) / frameRate;
                animationData.scale = Number(animationObject[utils.ConstValues.A_SCALE]);

                if (animationObject.hasOwnProperty(utils.ConstValues.A_TWEEN_EASING)) {
                    var tweenEase = animationObject[utils.ConstValues.A_TWEEN_EASING];
                    if (tweenEase == undefined || tweenEase == null) {
                        animationData.tweenEasing = NaN;
                    } else {
                        animationData.tweenEasing = Number(tweenEase);
                    }
                } else {
                    animationData.tweenEasing = NaN;
                }

                DataParser.parseTimeline(animationObject, animationData, DataParser.parseMainFrame, frameRate);

                var timeline;
                var timelineName;
                var timelineObjectList = animationObject[utils.ConstValues.TIMELINE];
                for (var index in timelineObjectList) {
                    var timelineObject = timelineObjectList[index];
                    timeline = DataParser.parseTransformTimeline(timelineObject, animationData.duration, frameRate);
                    timelineName = timelineObject[utils.ConstValues.A_NAME];
                    animationData.addTimeline(timeline, timelineName);
                }

                utils.DBDataUtil.addHideTimeline(animationData, armatureData);
                utils.DBDataUtil.transformAnimationData(animationData, armatureData);

                return animationData;
            };

            DataParser.parseTimeline = function (timelineObject, timeline, frameParser, frameRate) {
                var position = 0;
                var frame;
                var frameObjectList = timelineObject[utils.ConstValues.FRAME];
                for (var index in frameObjectList) {
                    var frameObject = frameObjectList[index];
                    frame = frameParser(frameObject, frameRate);
                    frame.position = position;
                    timeline.addFrame(frame);
                    position += frame.duration;
                }
                if (frame) {
                    frame.duration = timeline.duration - frame.position;
                }
            };

            DataParser.parseTransformTimeline = function (timelineObject, duration, frameRate) {
                var timeline = new TransformTimeline();
                timeline.duration = duration;

                DataParser.parseTimeline(timelineObject, timeline, DataParser.parseTransformFrame, frameRate);

                timeline.scale = Number(timelineObject[utils.ConstValues.A_SCALE]);
                timeline.offset = Number(timelineObject[utils.ConstValues.A_OFFSET]);

                return timeline;
            };

            DataParser.parseFrame = function (frameObject, frame, frameRate) {
                frame.duration = Number(frameObject[utils.ConstValues.A_DURATION]) / frameRate;
                frame.action = frameObject[utils.ConstValues.A_ACTION];
                frame.event = frameObject[utils.ConstValues.A_EVENT];
                frame.sound = frameObject[utils.ConstValues.A_SOUND];
            };

            DataParser.parseMainFrame = function (frameObject, frameRate) {
                var frame = new Frame();
                DataParser.parseFrame(frameObject, frame, frameRate);
                return frame;
            };

            DataParser.parseTransformFrame = function (frameObject, frameRate) {
                var frame = new TransformFrame();
                DataParser.parseFrame(frameObject, frame, frameRate);

                frame.visible = Number(frameObject[utils.ConstValues.A_HIDE]) != 1;

                if (frameObject.hasOwnProperty(utils.ConstValues.A_TWEEN_EASING)) {
                    var tweenEase = frameObject[utils.ConstValues.A_TWEEN_EASING];
                    if (tweenEase == undefined || tweenEase == null) {
                        frame.tweenEasing = NaN;
                    } else {
                        frame.tweenEasing = Number(tweenEase);
                    }
                } else {
                    frame.tweenEasing = 0;
                }

                frame.tweenRotate = Number(frameObject[utils.ConstValues.A_TWEEN_ROTATE]) || 0;
                frame.displayIndex = Number(frameObject[utils.ConstValues.A_DISPLAY_INDEX]) || 0;

                frame.zOrder = Number(frameObject[utils.ConstValues.A_Z_ORDER]) || 0;

                DataParser.parseTransform(frameObject[utils.ConstValues.TRANSFORM], frame.global, frame.pivot);
                frame.transform.copy(frame.global);

                var colorTransformObject = frameObject[utils.ConstValues.COLOR_TRANSFORM];
                if (colorTransformObject) {
                    frame.color = new geom.ColorTransform();
                    frame.color.alphaOffset = Number(colorTransformObject[utils.ConstValues.A_ALPHA_OFFSET]);
                    frame.color.redOffset = Number(colorTransformObject[utils.ConstValues.A_RED_OFFSET]);
                    frame.color.greenOffset = Number(colorTransformObject[utils.ConstValues.A_GREEN_OFFSET]);
                    frame.color.blueOffset = Number(colorTransformObject[utils.ConstValues.A_BLUE_OFFSET]);

                    frame.color.alphaMultiplier = Number(colorTransformObject[utils.ConstValues.A_ALPHA_MULTIPLIER]) * 0.01;
                    frame.color.redMultiplier = Number(colorTransformObject[utils.ConstValues.A_RED_MULTIPLIER]) * 0.01;
                    frame.color.greenMultiplier = Number(colorTransformObject[utils.ConstValues.A_GREEN_MULTIPLIER]) * 0.01;
                    frame.color.blueMultiplier = Number(colorTransformObject[utils.ConstValues.A_BLUE_MULTIPLIER]) * 0.01;
                }

                return frame;
            };

            DataParser.parseTransform = function (transformObject, transform, pivot) {
                if (typeof pivot === "undefined") { pivot = null; }
                if (transformObject) {
                    if (transform) {
                        transform.x = Number(transformObject[utils.ConstValues.A_X]);
                        transform.y = Number(transformObject[utils.ConstValues.A_Y]);
                        transform.skewX = Number(transformObject[utils.ConstValues.A_SKEW_X]) * utils.ConstValues.ANGLE_TO_RADIAN;
                        transform.skewY = Number(transformObject[utils.ConstValues.A_SKEW_Y]) * utils.ConstValues.ANGLE_TO_RADIAN;
                        transform.scaleX = Number(transformObject[utils.ConstValues.A_SCALE_X]);
                        transform.scaleY = Number(transformObject[utils.ConstValues.A_SCALE_Y]);
                    }
                    if (pivot) {
                        pivot.x = Number(transformObject[utils.ConstValues.A_PIVOT_X]);
                        pivot.y = Number(transformObject[utils.ConstValues.A_PIVOT_Y]);
                    }
                }
            };
            return DataParser;
        })();
        objects.DataParser = DataParser;
    })(dragonBones.objects || (dragonBones.objects = {}));
    var objects = dragonBones.objects;

    (function (factorys) {
        var BaseFactory = (function (_super) {
            __extends(BaseFactory, _super);
            function BaseFactory() {
                _super.call(this);

                this._dataDic = {};
                this._textureAtlasDic = {};
                this._textureAtlasLoadingDic = {};
            }
            BaseFactory.prototype.getSkeletonData = function (name) {
                return this._dataDic[name];
            };

            BaseFactory.prototype.addSkeletonData = function (data, name) {
                if (!data) {
                    throw new Error();
                }
                name = name || data.name;
                if (!name) {
                    throw new Error("Unnamed data!");
                }
                if (this._dataDic[name]) {
                }
                this._dataDic[name] = data;
            };

            BaseFactory.prototype.removeSkeletonData = function (name) {
                delete this._dataDic[name];
            };

            BaseFactory.prototype.getTextureAtlas = function (name) {
                return this._textureAtlasDic[name];
            };

            BaseFactory.prototype.addTextureAtlas = function (textureAtlas, name) {
                if (!textureAtlas) {
                    throw new Error();
                }

                name = name || textureAtlas.name;
                if (!name) {
                    throw new Error("Unnamed data!");
                }
                if (this._textureAtlasDic[name]) {
                }
                this._textureAtlasDic[name] = textureAtlas;
            };

            BaseFactory.prototype.removeTextureAtlas = function (name) {
                delete this._textureAtlasDic[name];
            };

            BaseFactory.prototype.dispose = function (disposeData) {
                if (typeof disposeData === "undefined") { disposeData = true; }
                if (disposeData) {
                    for (var i in this._dataDic) {
                        this._dataDic[i].dispose();
                    }
                    for (var i in this._textureAtlasDic) {
                        this._textureAtlasDic[i].dispose();
                    }
                }
                this._dataDic = null;
                this._textureAtlasDic = null;
                this._textureAtlasLoadingDic = null;
                this._currentDataName = null;
                this._currentTextureAtlasName = null;
            };

            BaseFactory.prototype.buildArmature = function (armatureName, animationName, skeletonName, textureAtlasName, skinName) {
                if (skeletonName) {
                    var data = this._dataDic[skeletonName];
                    if (data) {
                        var armatureData = data.getArmatureData(armatureName);
                    }
                } else {
                    for (skeletonName in this._dataDic) {
                        data = this._dataDic[skeletonName];
                        armatureData = data.getArmatureData(armatureName);
                        if (armatureData) {
                            break;
                        }
                    }
                }

                if (!armatureData) {
                    return null;
                }

                this._currentDataName = skeletonName;
                this._currentTextureAtlasName = textureAtlasName || skeletonName;

                var armature = this._generateArmature(armatureName);
                armature.name = armatureName;
                var bone;
                var boneData;
                var boneDataList = armatureData.getBoneDataList();
                for (var index in boneDataList) {
                    boneData = boneDataList[index];
                    bone = new dragonBones.Bone();
                    bone.name = boneData.name;
                    bone.origin.copy(boneData.transform);
                    if (armatureData.getBoneData(boneData.parent)) {
                        armature.addChild(bone, boneData.parent);
                    } else {
                        armature.addChild(bone, null);
                    }
                }

                if (animationName && animationName != armatureName) {
                    var animationArmatureData = data.getArmatureData(animationName);
                    if (!animationArmatureData) {
                        for (skeletonName in this._dataDic) {
                            data = this._dataDic[skeletonName];
                            animationArmatureData = data.getArmatureData(animationName);
                            if (animationArmatureData) {
                                break;
                            }
                        }
                    }
                }

                if (animationArmatureData) {
                    armature.animation.setAnimationDataList(animationArmatureData.getAnimationDataList());
                } else {
                    armature.animation.setAnimationDataList(armatureData.getAnimationDataList());
                }

                var skinData = armatureData.getSkinData(skinName);
                if (!skinData) {
                    throw new Error();
                }

                var slot;
                var displayData;
                var childArmature;
                var i;
                var helpArray = [];
                var slotData;
                var slotDataList = skinData.getSlotDataList();
                var displayDataList;
                for (var index in slotDataList) {
                    slotData = slotDataList[index];
                    bone = armature.getBone(slotData.parent);
                    if (!bone) {
                        continue;
                    }
                    displayDataList = slotData.getDisplayDataList();
                    slot = this._generateSlot();
                    slot.name = slotData.name;
                    slot._originZOrder = slotData.zOrder;
                    slot._dislayDataList = displayDataList;

                    helpArray.length = 0;
                    i = displayDataList.length;
                    while (i--) {
                        displayData = displayDataList[i];
                        displayData.name = '' + displayData.name; // 确保名称为字符串类型
                        switch (displayData.type) {
                            case objects.DisplayData.ARMATURE:
                                childArmature = this.buildArmature(displayData.name, null, this._currentDataName, this._currentTextureAtlasName, null);
                                if (childArmature) {
                                    helpArray[i] = childArmature;
                                }
                                break;
                            case objects.DisplayData.IMAGE:
                            default:
                                helpArray[i] = this._generateDisplay(this._textureAtlasDic[this._currentTextureAtlasName], displayData.name, displayData.pivot.x, displayData.pivot.y);
                                break;
                        }
                    }
                    slot.setDisplayList(helpArray);
                    slot._changeDisplay(0);
                    bone.addChild(slot);
                }

                armature._slotsZOrderChanged = true;
                armature.advanceTime(0);
                return armature;
            };

            BaseFactory.prototype.getTextureDisplay = function (textureName, textureAtlasName, pivotX, pivotY) {
                if (textureAtlasName) {
                    var textureAtlas = this._textureAtlasDic[textureAtlasName];
                }
                if (!textureAtlas && !textureAtlasName) {
                    for (textureAtlasName in this._textureAtlasDic) {
                        textureAtlas = this._textureAtlasDic[textureAtlasName];
                        if (textureAtlas.getRegion(textureName)) {
                            break;
                        }
                        textureAtlas = null;
                    }
                }
                if (textureAtlas) {
                    if (isNaN(pivotX) || isNaN(pivotY)) {
                        var data = this._dataDic[textureAtlasName];
                        if (data) {
                            var pivot = data.getSubTexturePivot(textureName);
                            if (pivot) {
                                pivotX = pivot.x;
                                pivotY = pivot.y;
                            }
                        }
                    }

                    return this._generateDisplay(textureAtlas, textureName, pivotX, pivotY);
                }
                return null;
            };

            BaseFactory.prototype._generateArmature = function (name) {
                return null;
            };

            BaseFactory.prototype._generateSlot = function () {
                return null;
            };

            BaseFactory.prototype._generateDisplay = function (textureAtlas, fullName, pivotX, pivotY) {
                return null;
            };
            return BaseFactory;
        })(events.EventDispatcher);
        factorys.BaseFactory = BaseFactory;
    })(dragonBones.factorys || (dragonBones.factorys = {}));
    var factorys = dragonBones.factorys;

    (function (utils) {
        var ConstValues = (function () {
            function ConstValues() {
            }
            ConstValues.ANGLE_TO_RADIAN = Math.PI / 180;

            ConstValues.DRAGON_BONES = "dragonBones";
            ConstValues.ARMATURE = "armature";
            ConstValues.SKIN = "skin";
            ConstValues.BONE = "bone";
            ConstValues.SLOT = "slot";
            ConstValues.DISPLAY = "display";
            ConstValues.ANIMATION = "animation";
            ConstValues.TIMELINE = "timeline";
            ConstValues.FRAME = "frame";
            ConstValues.TRANSFORM = "transform";
            ConstValues.COLOR_TRANSFORM = "colorTransform";

            ConstValues.TEXTURE_ATLAS = "TextureAtlas";
            ConstValues.SUB_TEXTURE = "SubTexture";

            ConstValues.A_VERSION = "version";
            ConstValues.A_IMAGE_PATH = "imagePath";
            ConstValues.A_FRAME_RATE = "frameRate";
            ConstValues.A_NAME = "name";
            ConstValues.A_PARENT = "parent";
            ConstValues.A_LENGTH = "length";
            ConstValues.A_TYPE = "type";
            ConstValues.A_FADE_IN_TIME = "fadeInTime";
            ConstValues.A_DURATION = "duration";
            ConstValues.A_SCALE = "scale";
            ConstValues.A_OFFSET = "offset";
            ConstValues.A_LOOP = "loop";
            ConstValues.A_EVENT = "event";
            ConstValues.A_SOUND = "sound";
            ConstValues.A_ACTION = "action";
            ConstValues.A_HIDE = "hide";
            ConstValues.A_TWEEN_EASING = "tweenEasing";
            ConstValues.A_TWEEN_ROTATE = "tweenRotate";
            ConstValues.A_DISPLAY_INDEX = "displayIndex";
            ConstValues.A_Z_ORDER = "z";
            ConstValues.A_WIDTH = "width";
            ConstValues.A_HEIGHT = "height";
            ConstValues.A_X = "x";
            ConstValues.A_Y = "y";
            ConstValues.A_SKEW_X = "skX";
            ConstValues.A_SKEW_Y = "skY";
            ConstValues.A_SCALE_X = "scX";
            ConstValues.A_SCALE_Y = "scY";
            ConstValues.A_PIVOT_X = "pX";
            ConstValues.A_PIVOT_Y = "pY";
            ConstValues.A_ALPHA_OFFSET = "aO";
            ConstValues.A_RED_OFFSET = "rO";
            ConstValues.A_GREEN_OFFSET = "gO";
            ConstValues.A_BLUE_OFFSET = "bO";
            ConstValues.A_ALPHA_MULTIPLIER = "aM";
            ConstValues.A_RED_MULTIPLIER = "rM";
            ConstValues.A_GREEN_MULTIPLIER = "gM";
            ConstValues.A_BLUE_MULTIPLIER = "bM";
            return ConstValues;
        })();
        utils.ConstValues = ConstValues;

        var TransformUtil = (function () {
            function TransformUtil() {
            }
            TransformUtil.transformPointWithParent = function (transform, parent) {
                var helpMatrix = TransformUtil._helpMatrix;
                TransformUtil.transformToMatrix(parent, helpMatrix);
                helpMatrix.invert();

                var x = transform.x;
                var y = transform.y;

                transform.x = helpMatrix.a * x + helpMatrix.c * y + helpMatrix.tx;
                transform.y = helpMatrix.d * y + helpMatrix.b * x + helpMatrix.ty;

                transform.skewX = TransformUtil.formatRadian(transform.skewX - parent.skewX);
                transform.skewY = TransformUtil.formatRadian(transform.skewY - parent.skewY);
            };

            TransformUtil.transformToMatrix = function (transform, matrix) {
                matrix.a = transform.scaleX * Math.cos(transform.skewY);
                matrix.b = transform.scaleX * Math.sin(transform.skewY);
                matrix.c = -transform.scaleY * Math.sin(transform.skewX);
                matrix.d = transform.scaleY * Math.cos(transform.skewX);
                matrix.tx = transform.x;
                matrix.ty = transform.y;
            };

            TransformUtil.formatRadian = function (radian) {
                radian %= TransformUtil.DOUBLE_PI;
                if (radian > Math.PI) {
                    radian -= TransformUtil.DOUBLE_PI;
                }
                if (radian < -Math.PI) {
                    radian += TransformUtil.DOUBLE_PI;
                }
                return radian;
            };
            TransformUtil.DOUBLE_PI = Math.PI * 2;
            TransformUtil._helpMatrix = new geom.Matrix();
            return TransformUtil;
        })();
        utils.TransformUtil = TransformUtil;

        var DBDataUtil = (function () {
            function DBDataUtil() {
            }
            DBDataUtil.transformArmatureData = function (armatureData) {
                var boneDataList = armatureData.getBoneDataList();
                var i = boneDataList.length;
                var boneData;
                var parentBoneData;
                while (i--) {
                    boneData = boneDataList[i];
                    if (boneData.parent) {
                        parentBoneData = armatureData.getBoneData(boneData.parent);
                        if (parentBoneData) {
                            boneData.transform.copy(boneData.global);
                            TransformUtil.transformPointWithParent(boneData.transform, parentBoneData.global);
                        }
                    }
                }
            };

            DBDataUtil.transformArmatureDataAnimations = function (armatureData) {
                var animationDataList = armatureData.getAnimationDataList();
                var i = animationDataList.length;
                while (i--) {
                    DBDataUtil.transformAnimationData(animationDataList[i], armatureData);
                }
            };

            DBDataUtil.transformAnimationData = function (animationData, armatureData) {
                var skinData = armatureData.getSkinData(null);
                var boneDataList = armatureData.getBoneDataList();
                var slotDataList = skinData.getSlotDataList();
                var i = boneDataList.length;

                var boneData;
                var timeline;
                var slotData;
                var displayData;
                var parentTimeline;
                var frameList;
                var originTransform;
                var originPivot;
                var prevFrame;
                var frame;
                var frameListLength;

                while (i--) {
                    boneData = boneDataList[i];
                    timeline = animationData.getTimeline(boneData.name);
                    if (!timeline) {
                        continue;
                    }

                    slotData = null;

                    for (var slotIndex in slotDataList) {
                        slotData = slotDataList[slotIndex];
                        if (slotData.parent == boneData.name) {
                            break;
                        }
                    }

                    parentTimeline = boneData.parent ? animationData.getTimeline(boneData.parent) : null;

                    frameList = timeline.getFrameList();

                    originTransform = null;
                    originPivot = null;
                    prevFrame = null;
                    frameListLength = frameList.length;
                    for (var j = 0; j < frameListLength; j++) {
                        frame = frameList[j];
                        if (parentTimeline) {
                            DBDataUtil._helpTransform1.copy(frame.global);

                            DBDataUtil.getTimelineTransform(parentTimeline, frame.position, DBDataUtil._helpTransform2);
                            TransformUtil.transformPointWithParent(DBDataUtil._helpTransform1, DBDataUtil._helpTransform2);

                            frame.transform.copy(DBDataUtil._helpTransform1);
                        } else {
                            frame.transform.copy(frame.global);
                        }

                        frame.transform.x -= boneData.transform.x;
                        frame.transform.y -= boneData.transform.y;
                        frame.transform.skewX -= boneData.transform.skewX;
                        frame.transform.skewY -= boneData.transform.skewY;
                        frame.transform.scaleX -= boneData.transform.scaleX;
                        frame.transform.scaleY -= boneData.transform.scaleY;

                        if (!timeline.transformed) {
                            if (slotData) {
                                frame.zOrder -= slotData.zOrder;
                            }
                        }

                        if (!originTransform) {
                            originTransform = timeline.originTransform;
                            originTransform.copy(frame.transform);
                            originTransform.skewX = TransformUtil.formatRadian(originTransform.skewX);
                            originTransform.skewY = TransformUtil.formatRadian(originTransform.skewY);
                            originPivot = timeline.originPivot;
                            originPivot.x = frame.pivot.x;
                            originPivot.y = frame.pivot.y;
                        }

                        frame.transform.x -= originTransform.x;
                        frame.transform.y -= originTransform.y;
                        frame.transform.skewX = TransformUtil.formatRadian(frame.transform.skewX - originTransform.skewX);
                        frame.transform.skewY = TransformUtil.formatRadian(frame.transform.skewY - originTransform.skewY);
                        frame.transform.scaleX -= originTransform.scaleX;
                        frame.transform.scaleY -= originTransform.scaleY;

                        if (!timeline.transformed) {
                            frame.pivot.x -= originPivot.x;
                            frame.pivot.y -= originPivot.y;
                        }

                        if (prevFrame) {
                            var dLX = frame.transform.skewX - prevFrame.transform.skewX;

                            if (prevFrame.tweenRotate) {
                                if (prevFrame.tweenRotate > 0) {
                                    if (dLX < 0) {
                                        frame.transform.skewX += Math.PI * 2;
                                        frame.transform.skewY += Math.PI * 2;
                                    }

                                    if (prevFrame.tweenRotate > 1) {
                                        frame.transform.skewX += Math.PI * 2 * (prevFrame.tweenRotate - 1);
                                        frame.transform.skewY += Math.PI * 2 * (prevFrame.tweenRotate - 1);
                                    }
                                } else {
                                    if (dLX > 0) {
                                        frame.transform.skewX -= Math.PI * 2;
                                        frame.transform.skewY -= Math.PI * 2;
                                    }

                                    if (prevFrame.tweenRotate < 1) {
                                        frame.transform.skewX += Math.PI * 2 * (prevFrame.tweenRotate + 1);
                                        frame.transform.skewY += Math.PI * 2 * (prevFrame.tweenRotate + 1);
                                    }
                                }
                            } else {
                                frame.transform.skewX = prevFrame.transform.skewX + TransformUtil.formatRadian(frame.transform.skewX - prevFrame.transform.skewX);
                                frame.transform.skewY = prevFrame.transform.skewY + TransformUtil.formatRadian(frame.transform.skewY - prevFrame.transform.skewY);
                            }
                        }

                        prevFrame = frame;
                    }
                    timeline.transformed = true;
                }
            };

            DBDataUtil.getTimelineTransform = function (timeline, position, retult) {
                var frameList = timeline.getFrameList();
                var i = frameList.length;

                var currentFrame;
                var tweenEasing;
                var progress;
                var nextFrame;
                while (i--) {
                    currentFrame = frameList[i];
                    if (currentFrame.position <= position && currentFrame.position + currentFrame.duration > position) {
                        tweenEasing = currentFrame.tweenEasing;
                        if (i == frameList.length - 1 || isNaN(tweenEasing) || position == currentFrame.position) {
                            retult.copy(currentFrame.global);
                        } else {
                            progress = (position - currentFrame.position) / currentFrame.duration;
                            if (tweenEasing) {
                                progress = animation.TimelineState.getEaseValue(progress, tweenEasing);
                            }

                            nextFrame = frameList[i + 1];

                            retult.x = currentFrame.global.x + (nextFrame.global.x - currentFrame.global.x) * progress;
                            retult.y = currentFrame.global.y + (nextFrame.global.y - currentFrame.global.y) * progress;
                            retult.skewX = TransformUtil.formatRadian(currentFrame.global.skewX + (nextFrame.global.skewX - currentFrame.global.skewX) * progress);
                            retult.skewY = TransformUtil.formatRadian(currentFrame.global.skewY + (nextFrame.global.skewY - currentFrame.global.skewY) * progress);
                            retult.scaleX = currentFrame.global.scaleX + (nextFrame.global.scaleX - currentFrame.global.scaleX) * progress;
                            retult.scaleY = currentFrame.global.scaleY + (nextFrame.global.scaleY - currentFrame.global.scaleY) * progress;
                        }
                        break;
                    }
                }
            };

            DBDataUtil.addHideTimeline = function (animationData, armatureData) {
                var boneDataList = armatureData.getBoneDataList();
                var i = boneDataList.length;

                var boneData;
                var boneName;
                while (i--) {
                    boneData = boneDataList[i];
                    boneName = boneData.name;
                    if (!animationData.getTimeline(boneName)) {
                        animationData.addTimeline(objects.TransformTimeline.HIDE_TIMELINE, boneName);
                    }
                }
            };
            DBDataUtil._helpTransform1 = new objects.DBTransform();
            DBDataUtil._helpTransform2 = new objects.DBTransform();
            return DBDataUtil;
        })();
        utils.DBDataUtil = DBDataUtil;
    })(dragonBones.utils || (dragonBones.utils = {}));
    var utils = dragonBones.utils;

    var DBObject = (function () {
        function DBObject() {
            this.global = new objects.DBTransform();
            this.origin = new objects.DBTransform();
            this.offset = new objects.DBTransform();
            this.tween = new objects.DBTransform();
            this.tween.scaleX = this.tween.scaleY = 0;

            this._globalTransformMatrix = new geom.Matrix();

            this._visible = true;
            this._isColorChanged = false;
            this._isDisplayOnStage = false;
            this._scaleType = 0;

            this.fixedRotation = false;
        }
        DBObject.prototype.getVisible = function () {
            return this._visible;
        };
        DBObject.prototype.setVisible = function (value) {
            this._visible = value;
        };

        DBObject.prototype._setParent = function (value) {
            this.parent = value;
        };

        DBObject.prototype._setArmature = function (value) {
            if (this.armature) {
                this.armature._removeDBObject(this);
            }
            this.armature = value;
            if (this.armature) {
                this.armature._addDBObject(this);
            }
        };

        DBObject.prototype.dispose = function () {
            this.parent = null;
            this.armature = null;
            this.global = null;
            this.origin = null;
            this.offset = null;
            this.tween = null;
            this._globalTransformMatrix = null;
        };

        DBObject.prototype._update = function () {
            this.global.scaleX = (this.origin.scaleX + this.tween.scaleX) * this.offset.scaleX;
            this.global.scaleY = (this.origin.scaleY + this.tween.scaleY) * this.offset.scaleY;

            if (this.parent) {
                var x = this.origin.x + this.offset.x + this.tween.x;
                var y = this.origin.y + this.offset.y + this.tween.y;
                var parentMatrix = this.parent._globalTransformMatrix;

                this._globalTransformMatrix.tx = this.global.x = parentMatrix.a * x + parentMatrix.c * y + parentMatrix.tx;
                this._globalTransformMatrix.ty = this.global.y = parentMatrix.d * y + parentMatrix.b * x + parentMatrix.ty;

                if (this.fixedRotation) {
                    this.global.skewX = this.origin.skewX + this.offset.skewX + this.tween.skewX;
                    this.global.skewY = this.origin.skewY + this.offset.skewY + this.tween.skewY;
                } else {
                    this.global.skewX = this.origin.skewX + this.offset.skewX + this.tween.skewX + this.parent.global.skewX;
                    this.global.skewY = this.origin.skewY + this.offset.skewY + this.tween.skewY + this.parent.global.skewY;
                }

                if (this.parent.scaleMode >= this._scaleType) {
                    this.global.scaleX *= this.parent.global.scaleX;
                    this.global.scaleY *= this.parent.global.scaleY;
                }
            } else {
                this._globalTransformMatrix.tx = this.global.x = this.origin.x + this.offset.x + this.tween.x;
                this._globalTransformMatrix.ty = this.global.y = this.origin.y + this.offset.y + this.tween.y;

                this.global.skewX = this.origin.skewX + this.offset.skewX + this.tween.skewX;
                this.global.skewY = this.origin.skewY + this.offset.skewY + this.tween.skewY;
            }
            this._globalTransformMatrix.a = this.global.scaleX * Math.cos(this.global.skewY);
            this._globalTransformMatrix.b = this.global.scaleX * Math.sin(this.global.skewY);
            this._globalTransformMatrix.c = -this.global.scaleY * Math.sin(this.global.skewX);
            this._globalTransformMatrix.d = this.global.scaleY * Math.cos(this.global.skewX);
        };
        return DBObject;
    })();
    dragonBones.DBObject = DBObject;

    var Slot = (function (_super) {
        __extends(Slot, _super);
        function Slot(displayBrideg) {
            _super.call(this);
            this._displayBridge = displayBrideg;
            this._displayList = [];
            this._displayIndex = -1;
            this._scaleType = 1;

            this._originZOrder = 0;
            this._tweenZorder = 0;
            this._offsetZOrder = 0;

            this._isDisplayOnStage = false;
            this._isHideDisplay = false;
        }
        Slot.prototype.getZOrder = function () {
            return this._originZOrder + this._tweenZorder + this._offsetZOrder;
        };

        Slot.prototype.setZOrder = function (value) {
            if (this.getZOrder() != value) {
                this._offsetZOrder = value - this._originZOrder - this._tweenZorder;
                if (this.armature) {
                    this.armature._slotsZOrderChanged = true;
                }
            }
        };

        Slot.prototype.getDisplay = function () {
            var display = this._displayList[this._displayIndex];
            if (display instanceof Armature) {
                return (display).getDisplay();
            }
            return display;
        };
        Slot.prototype.setDisplay = function (value) {
            this._displayList[this._displayIndex] = value;
            this._setDisplay(value);
        };

        Slot.prototype.getChildArmature = function () {
            var display = this._displayList[this._displayIndex];
            if (display instanceof Armature) {
                return display;
            }
            return null;
        };
        Slot.prototype.setChildArmature = function (value) {
            this._displayList[this._displayIndex] = value;
            if (value) {
                this._setDisplay(value.getDisplay());
            }
        };

        Slot.prototype.getDisplayList = function () {
            return this._displayList;
        };
        Slot.prototype.setDisplayList = function (value) {
            if (!value) {
                throw new Error();
            }
            var i = this._displayList.length = value.length;
            while (i--) {
                this._displayList[i] = value[i];
            }
            if (this._displayIndex >= 0) {
                var displayIndexBackup = this._displayIndex;
                this._displayIndex = -1;
                this._changeDisplay(displayIndexBackup);
            }
        };

        Slot.prototype._setDisplay = function (display) {
            if (this._displayBridge.getDisplay()) {
                this._displayBridge.setDisplay(display);
            } else {
                this._displayBridge.setDisplay(display);
                if (this.armature) {
                    this._displayBridge.addDisplay(this.armature.getDisplay(), -1);
                    this.armature._slotsZOrderChanged = true;
                }
            }

            this.updateChildArmatureAnimation();

            if (!this._isHideDisplay && this._displayBridge.getDisplay()) {
                this._isDisplayOnStage = true;
            } else {
                this._isDisplayOnStage = false;
            }
        };

        Slot.prototype._changeDisplay = function (displayIndex) {
            if (displayIndex < 0) {
                if (!this._isHideDisplay) {
                    this._isHideDisplay = true;
                    this._displayBridge.removeDisplay();
                    this.updateChildArmatureAnimation();
                }
            } else {
                if (this._isHideDisplay) {
                    this._isHideDisplay = false;
                    var changeShowState = true;
                    if (this.armature) {
                        this._displayBridge.addDisplay(this.armature.getDisplay(), -1);
                        this.armature._slotsZOrderChanged = true;
                    }
                }

                var length = this._displayList.length;
                if (displayIndex >= length && length > 0) {
                    displayIndex = length - 1;
                }
                if (this._displayIndex != displayIndex) {
                    this._displayIndex = displayIndex;

                    var display = this._displayList[this._displayIndex];
                    if (display instanceof Armature) {
                        this._setDisplay((display).getDisplay());
                    } else {
                        this._setDisplay(display);
                    }

                    if (this._dislayDataList && this._displayIndex < this._dislayDataList.length) {
                        this.origin.copy(this._dislayDataList[this._displayIndex].transform);
                    }
                } else if (changeShowState) {
                    this.updateChildArmatureAnimation();
                }
            }

            if (!this._isHideDisplay && this._displayBridge.getDisplay()) {
                this._isDisplayOnStage = true;
            } else {
                this._isDisplayOnStage = false;
            }
        };

        Slot.prototype.setVisible = function (value) {
            if (value != this._visible) {
                this._visible = value;
                this._updateVisible(this._visible);
            }
        };

        Slot.prototype._setArmature = function (value) {
            _super.prototype._setArmature.call(this, value);
            if (this.armature) {
                this.armature._slotsZOrderChanged = true;
                this._displayBridge.addDisplay(this.armature.getDisplay(), -1);
            } else {
                this._displayBridge.removeDisplay();
            }
        };

        Slot.prototype.dispose = function () {
            if (!this._displayBridge) {
                return;
            }
            _super.prototype.dispose.call(this);

            this._displayBridge.dispose();
            this._displayList.length = 0;

            this._displayBridge = null;
            this._displayList = null;
            this._dislayDataList = null;
        };

        Slot.prototype._update = function () {
            _super.prototype._update.call(this);
            if (this._isDisplayOnStage) {
                var pivotX = this.parent._tweenPivot.x;
                var pivotY = this.parent._tweenPivot.y;
                if (pivotX || pivotY) {
                    var parentMatrix = this.parent._globalTransformMatrix;
                    this._globalTransformMatrix.tx += parentMatrix.a * pivotX + parentMatrix.c * pivotY;
                    this._globalTransformMatrix.ty += parentMatrix.b * pivotX + parentMatrix.d * pivotY;
                }

                this._displayBridge.updateTransform(this._globalTransformMatrix, this.global);
            }
        };

        Slot.prototype._updateVisible = function (value) {
            this._displayBridge.setVisible(this.parent.getVisible() && this._visible && value);
        };

        Slot.prototype.updateChildArmatureAnimation = function () {
            var childArmature = this.getChildArmature();

            if (childArmature) {
                if (this._isHideDisplay) {
                    childArmature.animation.stop();
                    childArmature.animation._lastAnimationState = null;
                } else {
                    var lastAnimationName = this.armature ? this.armature.animation.getLastAnimationName() : null;
                    if (lastAnimationName && childArmature.animation.hasAnimation(lastAnimationName)) {
                        childArmature.animation.gotoAndPlay(lastAnimationName);
                    } else {
                        childArmature.animation.play();
                    }
                }
            }
        };
        return Slot;
    })(DBObject);
    dragonBones.Slot = Slot;

    var Bone = (function (_super) {
        __extends(Bone, _super);
        function Bone() {
            _super.call(this);
            this._children = [];
            this._scaleType = 2;

            this._tweenPivot = new geom.Point();

            this.scaleMode = 1;
        }
        Bone.prototype.setVisible = function (value) {
            if (this._visible != value) {
                this._visible = value;
                var i = this._children.length;
                while (i--) {
                    var child = this._children[i];
                    if (child instanceof Slot) {
                        (child)._updateVisible(this._visible);
                    }
                }
            }
        };

        Bone.prototype._setArmature = function (value) {
            _super.prototype._setArmature.call(this, value);
            var i = this._children.length;
            while (i--) {
                this._children[i]._setArmature(this.armature);
            }
        };

        Bone.prototype.dispose = function () {
            if (!this._children) {
                return;
            }
            _super.prototype.dispose.call(this);

            var i = this._children.length;
            while (i--) {
                this._children[i].dispose();
            }
            this._children.length = 0;

            this._children = null;
            this._tweenPivot = null;

            this.slot = null;
        };

        Bone.prototype.contains = function (child) {
            if (!child) {
                throw new Error();
            }
            if (child == this) {
                return false;
            }
            var ancestor = child;
            while (!(ancestor == this || ancestor == null)) {
                ancestor = ancestor.parent;
            }
            return ancestor == this;
        };

        Bone.prototype.addChild = function (child) {
            if (!child) {
                throw new Error();
            }

            if (child == this || (child instanceof Bone && (child).contains(this))) {
                throw new Error("An Bone cannot be added as a child to itself or one of its children (or children's children, etc.)");
            }

            if (child.parent) {
                child.parent.removeChild(child);
            }
            this._children[this._children.length] = child;
            child._setParent(this);
            child._setArmature(this.armature);

            if (!this.slot && child instanceof Slot) {
                this.slot = child;
            }
        };

        Bone.prototype.removeChild = function (child) {
            if (!child) {
                throw new Error();
            }

            var index = this._children.indexOf(child);
            if (index >= 0) {
                this._children.splice(index, 1);
                child._setParent(null);
                child._setArmature(null);

                if (child == this.slot) {
                    this.slot = null;
                }
            } else {
                throw new Error();
            }
        };

        Bone.prototype.getSlots = function () {
            var slotList = [];
            var i = this._children.length;
            while (i--) {
                if (this._children[i] instanceof Slot) {
                    slotList.unshift(this._children[i]);
                }
            }
            return slotList;
        };

        Bone.prototype._arriveAtFrame = function (frame, timelineState, animationState, isCross) {
            if (frame) {
                var mixingType = animationState.getMixingTransform(name);
                if (animationState.displayControl && (mixingType == 2 || mixingType == -1)) {
                    if (!this.displayController || this.displayController == animationState.name) {
                        var tansformFrame = frame;
                        if (this.slot) {
                            var displayIndex = tansformFrame.displayIndex;
                            if (displayIndex >= 0) {
                                if (!isNaN(tansformFrame.zOrder) && tansformFrame.zOrder != this.slot._tweenZorder) {
                                    this.slot._tweenZorder = tansformFrame.zOrder;
                                    this.armature._slotsZOrderChanged = true;
                                }
                            }
                            this.slot._changeDisplay(displayIndex);
                            this.slot._updateVisible(tansformFrame.visible);
                        }
                    }
                }

                if (frame.event && this.armature.hasEventListener(events.FrameEvent.BONE_FRAME_EVENT)) {
                    var frameEvent = new events.FrameEvent(events.FrameEvent.BONE_FRAME_EVENT);
                    frameEvent.bone = this;
                    frameEvent.animationState = animationState;
                    frameEvent.frameLabel = frame.event;
                    this.armature._eventList.push(frameEvent);
                }

                if (frame.sound && Bone._soundManager.hasEventListener(events.SoundEvent.SOUND)) {
                    var soundEvent = new events.SoundEvent(events.SoundEvent.SOUND);
                    soundEvent.armature = this.armature;
                    soundEvent.animationState = animationState;
                    soundEvent.sound = frame.sound;
                    Bone._soundManager.dispatchEvent(soundEvent);
                }

                if (frame.action) {
                    for (var index in this._children) {
                        if (this._children[index] instanceof Slot) {
                            var childArmature = (this._children[index]).getChildArmature();
                            if (childArmature) {
                                childArmature.animation.gotoAndPlay(frame.action);
                            }
                        }
                    }
                }
            } else {
                if (this.slot) {
                    this.slot._changeDisplay(-1);
                }
            }
        };

        Bone.prototype._updateColor = function (aOffset, rOffset, gOffset, bOffset, aMultiplier, rMultiplier, gMultiplier, bMultiplier, isColorChanged) {
            var i = this._children.length;
            while (i--) {
                var child = this._children[i];
                if (child instanceof Slot) {
                    (child)._displayBridge.updateColor(aOffset, rOffset, gOffset, bOffset, aMultiplier, rMultiplier, gMultiplier, bMultiplier);
                }
            }
            this._isColorChanged = isColorChanged;
        };
        Bone._soundManager = events.SoundEventManager.getInstance();
        return Bone;
    })(DBObject);
    dragonBones.Bone = Bone;

    var Armature = (function (_super) {
        __extends(Armature, _super);
        function Armature(display) {
            _super.call(this);

            this.animation = new animation.Animation(this);

            this._display = display;
            this._slotsZOrderChanged = false;
            this._slotList = [];
            this._boneList = [];
            this._eventList = [];
        }
        Armature.prototype.getDisplay = function () {
            return this._display;
        };

        Armature.prototype.dispose = function () {
            if (!this.animation) {
                return;
            }
            this.animation.dispose();

            var i = this._slotList.length;
            while (i--) {
                this._slotList[i].dispose();
            }

            i = this._boneList.length;
            while (i--) {
                this._boneList[i].dispose();
            }

            this._slotList.length = 0;
            this._boneList.length = 0;
            this._eventList.length = 0;

            this._slotList = null;
            this._boneList = null;
            this._eventList = null;
            this._display = null;

            this.animation = null;
        };

        Armature.prototype.advanceTime = function (passedTime) {
            this.animation.advanceTime(passedTime);
            passedTime *= this.animation.timeScale;

            var i = this._boneList.length;
            while (i--) {
                this._boneList[i]._update();
            }
            i = this._slotList.length;
            var slot;
            while (i--) {
                slot = this._slotList[i];
                slot._update();
                if (slot._isDisplayOnStage) {
                    var childArmature = slot.getChildArmature();
                    if (childArmature) {
                        childArmature.advanceTime(passedTime);
                    }
                }
            }

            if (this._slotsZOrderChanged) {
                this.updateSlotsZOrder();
                if (this.hasEventListener(events.ArmatureEvent.Z_ORDER_UPDATED)) {
                    this.dispatchEvent(new events.ArmatureEvent(events.ArmatureEvent.Z_ORDER_UPDATED));
                }
            }

            if (this._eventList.length) {
                var length = this._eventList.length;
                for (i = 0; i < length; i++) {
                    this.dispatchEvent(this._eventList[i]);
                }
                this._eventList.length = 0;
            }
        };

        Armature.prototype.getSlots = function (returnCopy) {
            if (typeof returnCopy === "undefined") { returnCopy = true; }
            return returnCopy ? this._slotList.concat() : this._slotList;
        };

        Armature.prototype.getBones = function (returnCopy) {
            if (typeof returnCopy === "undefined") { returnCopy = true; }
            return returnCopy ? this._boneList.concat() : this._boneList;
        };

        Armature.prototype.getSlot = function (slotName) {
            var i = this._slotList.length;
            while (i--) {
                if (this._slotList[i].name == slotName) {
                    return this._slotList[i];
                }
            }
            return null;
        };

        Armature.prototype.getSlotByDisplay = function (display) {
            if (display) {
                var i = this._slotList.length;
                while (i--) {
                    if (this._slotList[i].getDisplay() == display) {
                        return this._slotList[i];
                    }
                }
            }
            return null;
        };

        Armature.prototype.removeSlot = function (slot) {
            if (!slot) {
                throw new Error();
            }

            if (this._slotList.indexOf(slot) >= 0) {
                slot.parent.removeChild(slot);
            } else {
                throw new Error();
            }
        };

        Armature.prototype.removeSlotByName = function (slotName) {
            if (!slotName) {
                return;
            }

            var slot = this.getSlot(slotName);
            if (slot) {
                this.removeSlot(slot);
            }
        };

        Armature.prototype.getBone = function (boneName) {
            var i = this._boneList.length;
            while (i--) {
                if (this._boneList[i].name == boneName) {
                    return this._boneList[i];
                }
            }
            return null;
        };

        Armature.prototype.getBoneByDisplay = function (display) {
            var slot = this.getSlotByDisplay(display);
            return slot ? slot.parent : null;
        };

        Armature.prototype.removeBone = function (bone) {
            if (!bone) {
                throw new Error();
            }

            if (this._boneList.indexOf(bone) >= 0) {
                if (bone.parent) {
                    bone.parent.removeChild(bone);
                } else {
                    bone._setArmature(null);
                }
            } else {
                throw new Error();
            }
        };

        Armature.prototype.removeBoneByName = function (boneName) {
            if (!boneName) {
                return;
            }

            var bone = this.getBone(boneName);
            if (bone) {
                this.removeBone(bone);
            }
        };

        Armature.prototype.addChild = function (object, parentName) {
            if (!object) {
                throw new Error();
            }
            if (parentName) {
                var boneParent = this.getBone(parentName);
                if (boneParent) {
                    boneParent.addChild(object);
                } else {
                    throw new Error();
                }
            } else {
                if (object.parent) {
                    object.parent.removeChild(object);
                }
                object._setArmature(this);
            }
        };

        Armature.prototype.updateSlotsZOrder = function () {
            this._slotList.sort(this.sortSlot);
            var i = this._slotList.length;
            var slot;
            while (i--) {
                slot = this._slotList[i];
                if (slot._isDisplayOnStage) {
                    slot._displayBridge.addDisplay(this._display, -1);
                }
            }

            this._slotsZOrderChanged = false;
        };

        Armature.prototype._addDBObject = function (object) {
            if (object instanceof Slot) {
                var slot = object;
                if (this._slotList.indexOf(slot) < 0) {
                    this._slotList[this._slotList.length] = slot;
                }
            } else if (object instanceof Bone) {
                var bone = object;
                if (this._boneList.indexOf(bone) < 0) {
                    this._boneList[this._boneList.length] = bone;
                    this._sortBoneList();
                }
            }
        };

        Armature.prototype._removeDBObject = function (object) {
            if (object instanceof Slot) {
                var slot = object;
                var index = this._slotList.indexOf(slot);
                if (index >= 0) {
                    this._slotList.splice(index, 1);
                }
            } else if (object instanceof Bone) {
                var bone = object;
                index = this._boneList.indexOf(bone);
                if (index >= 0) {
                    this._boneList.splice(index, 1);
                }
            }
        };

        Armature.prototype._sortBoneList = function () {
            var i = this._boneList.length;
            if (i == 0) {
                return;
            }
            var helpArray = [];
            var level;
            var bone;
            var boneParent;
            while (i--) {
                level = 0;
                bone = this._boneList[i];
                boneParent = bone;
                while (boneParent) {
                    level++;
                    boneParent = boneParent.parent;
                }
                helpArray[i] = { level: level, bone: bone };
            }

            helpArray.sort(this.sortBone);

            i = helpArray.length;
            while (i--) {
                this._boneList[i] = helpArray[i].bone;
            }
        };

        Armature.prototype._arriveAtFrame = function (frame, timelineState, animationState, isCross) {
            if (frame.event && this.hasEventListener(events.FrameEvent.ANIMATION_FRAME_EVENT)) {
                var frameEvent = new events.FrameEvent(events.FrameEvent.ANIMATION_FRAME_EVENT);
                frameEvent.animationState = animationState;
                frameEvent.frameLabel = frame.event;
                this._eventList.push(frameEvent);
            }

            if (frame.sound && Armature._soundManager.hasEventListener(events.SoundEvent.SOUND)) {
                var soundEvent = new events.SoundEvent(events.SoundEvent.SOUND);
                soundEvent.armature = this;
                soundEvent.animationState = animationState;
                soundEvent.sound = frame.sound;
                Armature._soundManager.dispatchEvent(soundEvent);
            }

            if (frame.action) {
                if (animationState.isPlaying) {
                    this.animation.gotoAndPlay(frame.action);
                }
            }
        };

        Armature.prototype.sortSlot = function (slot1, slot2) {
            return slot1.getZOrder() < slot2.getZOrder() ? 1 : -1;
        };

        Armature.prototype.sortBone = function (object1, object2) {
            return object1.level < object2.level ? 1 : -1;
        };
        Armature._soundManager = events.SoundEventManager.getInstance();
        return Armature;
    })(events.EventDispatcher);
    dragonBones.Armature = Armature;
})(dragonBones || (dragonBones = {}));

var dragonBones;
(function (dragonBones) {
    (function (display) {
        var QcDisplayBridge = (function () {
            function QcDisplayBridge() {
            }

            QcDisplayBridge.prototype.getVisible = function () {
                return this._display ? this._display.visible : false
            };
            QcDisplayBridge.prototype.setVisible = function (value) {
                if (this._display) {
                    this._display.visible = value
                }
            };
            QcDisplayBridge.prototype.getDisplay = function () {
                return this._display
            };
            QcDisplayBridge.prototype.setDisplay = function (value) {
                if (this._display == value) {
                    return
                }
                var index = -1;
                if (this._display) {
                    var parent = this._display.parent;
                    if (parent) {
                        index = this._display.parent.children.indexOf(this._display);
                    }
                    this.removeDisplay()
                }
                this._display = value;
                this.addDisplay(parent, index)
            };
            QcDisplayBridge.prototype.dispose = function () {
                this._display = null
            };
            QcDisplayBridge.prototype.updateTransform = function (matrix, transform) {
                // apply the matrix to the qc game display object
                this._display.x = matrix.tx;
                this._display.y = matrix.ty;
                this._display.skewX = transform.skewX;
                this._display.skewY = transform.skewY;
                this._display.scaleX = transform.scaleX;
                this._display.scaleY = transform.scaleY;
            };
            QcDisplayBridge.prototype.updateColor = function (aOffset, rOffset, gOffset, bOffset, aMultiplier, rMultiplier, gMultiplier, bMultiplier) {
                if (this._display) {
                    this._display.alpha = aMultiplier
                }
            };
            QcDisplayBridge.prototype.addDisplay = function (container, index) {
                var parent = container;
                if (parent && this._display) {
                    if (index < 0 || typeof index === "undefined") {
                        if (this._display.parent === parent)
                            parent.removeChild(this._display);
                        parent.addChild(this._display)
                    } else {
                        parent.addChildAt(this._display, Math.min(index, parent.children.length))
                    }
                }
            };
            QcDisplayBridge.prototype.removeDisplay = function () {
                if (this._display && this._display.parent) {
                    this._display.parent.removeChild(this._display)
                }
            };
            QcDisplayBridge.RADIAN_TO_ANGLE = 180 / Math.PI;
            return QcDisplayBridge
        })();
        display.QcDisplayBridge = QcDisplayBridge
    })(dragonBones.display || (dragonBones.display = {}));
    var display = dragonBones.display;
    (function (textures) {
        var QcBonesAtlas = (function () {
            function QcBonesAtlas(image, textureAtlasRawData, scale) {
                if (typeof scale === "undefined") {
                    scale = 1
                }
                this._regions = {};
                this.image = image;
                this.scale = scale;
                this.atlasId = textureAtlasRawData.atlasId;
                this.parseData(textureAtlasRawData)
            }

            QcBonesAtlas.prototype.dispose = function () {
                this.image = null;
                this._regions = null
            };
            QcBonesAtlas.prototype.getRegion = function (subTextureName) {
                return this._regions[subTextureName]
            };
            QcBonesAtlas.prototype.parseData = function (textureAtlasRawData) {
                var textureAtlasData = dragonBones.objects.DataParser.parseTextureAtlasData(textureAtlasRawData, this.scale);
                this.name = textureAtlasData.__name;
                delete textureAtlasData.__name;
                for (var subTextureName in textureAtlasData) {
                    this._regions[subTextureName] = textureAtlasData[subTextureName]
                }
            };
            return QcBonesAtlas
        })();
        textures.QcBonesAtlas = QcBonesAtlas
    })(dragonBones.textures || (dragonBones.textures = {}));
    var textures = dragonBones.textures;
    (function (factorys) {
        var QcBonesFactory = (function (_super) {
            __extends(QcBonesFactory, _super);
            function QcBonesFactory() {
                _super.call(this)
            }

            QcBonesFactory.prototype._generateArmature = function (armatureName) {
                var display = new qc.Node(new Phaser.Group(dragonBones.game, null));
                var armature = new dragonBones.Armature(display);
                display.name = armatureName;
                display._bone = true;
                return armature
            };
            QcBonesFactory.prototype._generateSlot = function () {
                var slot = new dragonBones.Slot(new display.QcDisplayBridge());
                return slot
            };
            QcBonesFactory.prototype._generateDisplay = function (textureAtlas, frameName, pivotX, pivotY) {
                // 创建一个贴图
                var image = new qc.UIImage(dragonBones.game._qc);

                // 载入对应的帧
                var imgName = textureAtlas.atlasId;
                image.texture = image.game.assets.find(imgName);
                image.frame = frameName;
                image.name = frameName;
                image._bone = true;
                image.resetNativeSize();

                // 设置旋转轴
                image.pivotX = pivotX / image.width;
                image.pivotY = pivotY / image.height;

                if (image.parent) {
                    image.parent.removeChild(image);
                }
                return image;
            };
            return QcBonesFactory
        })(factorys.BaseFactory);
        factorys.QcBonesFactory = QcBonesFactory
    })(dragonBones.factorys || (dragonBones.factorys = {}));
    var factorys = dragonBones.factorys
})(dragonBones || (dragonBones = {}));

//----------------------------------

/**
 * 将 DragonBone 的格式转换为 Phaser 接受的格式，用于驱动 frameData
 */
dragonBones.CoverAtlas = function(atlasJson) {
    var frames;
    var atlas = {
        'meta' : { 'image' : atlasJson.imagePath },
        'frames' : frames = {}
    };

    // 往 frames 填充内容
    var textures = atlasJson.SubTexture;
    var len = textures.length;
    var frameData;
    for (var i = 0; i < len; i++) {
        frameData = textures[i];
        frames[frameData.name] = {
            "filename" : frameData.name,
            "frame" : { "x" : frameData.x, "y" : frameData.y, "w" : frameData.width, "h" : frameData.height },
            "rotated": false,
            "trimmed": false,
            "spriteSourceSize": { "x" : 0, "y" : 0, "w" : frameData.width, "h" : frameData.height },
            "sourceSize": { "w" : frameData.width, "h" : frameData.height }
        };
    }

    return atlas;
};

/**
 * 工具类，寻找一块骨骼节点
 */
dragonBones.findArmature = function(skeleton) {
    return skeleton.armature[0].name;
};

/**
 * 工具类，用于创建骨骼
 */
dragonBones.makeQcArmature = function(skeletonData, atlasJson, texture, key) {
    // 图片的关联需要修改下
    atlasJson.name = key;
    skeletonData.name = key;
    var textureData = atlasJson;
    textureData.atlasId = textureData.name;// set the is

    // 创建工厂，提供骨骼、贴图信息
    var factory = new dragonBones.factorys.QcBonesFactory();
    factory.addSkeletonData(dragonBones.objects.DataParser.parseSkeletonData(skeletonData));

    var atlas = new dragonBones.textures.QcBonesAtlas(texture, textureData);
    factory.addTextureAtlas(atlas);

    // 生成骨骼网络
    var armature = factory.buildArmature(dragonBones.findArmature(skeletonData));

    // 加入到时钟中开始供驱动
    // dragonBones.animation.WorldClock.clock.add(armature);

    return armature;
};

// 将命名空间注册到qc中供全局访问
qc.dragonBones = dragonBones;

// 插件方式，注册dragonBones的game元素以及驱动Update
qc.dragonBonesDriver = function(game, parent) {
    qc.dragonBones.game = game;
};
qc.dragonBonesDriver.prototype = {
    update : function() {
        // qc.dragonBones.animation.WorldClock.clock.advanceTime(0.02);
    }
};
qc.dragonBonesDriver.prototype.constructor = qc.dragonBonesDriver;

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 组件的交互变换模式
 * 不切换、颜色混合、图片变换、自定义动作
 * @class qc.Transition
 */
qc.Transition = {
    /**
     * @property {number} NONE - 不需要有任何变换
     * @static
     */
    NONE : 0,

    /**
     * @property {number} COLOR_TINT - 变换颜色
     * @static
     */
    COLOR_TINT : 1,

    /**
     * @property {number} TEXTURE_SWAP - 使用图片变换
     * @static
     */
    TEXTURE_SWAP : 2,

    /**
     * @property {number} ANIMATION - 使用自定义动画
     * @static
     */
    ANIMATION : 3
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 组件的3种状态：
 * 普通、按下、不可交互
 *
 * @class qc.UIState
 */
qc.UIState = {
    /**
     * @property {number} NORMAL - 正常状态
     * @static
     */
    NORMAL : 0,

    /**
     * @property {number} PRESSED - 按下状态
     * @static
     */
    PRESSED : 1,

    /**
     * @property {number} DISABLED - 不可用
     * @static
     */
    DISABLED : 2
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 工具库
 */
qc.Util = {

    /**
     * 根据类的名字查找类对象
     * @param name
     */
    findClass : function(name) {
        var arr = name.split('.');
        var curr = window;
        for (var i = 0; i < arr.length - 1; i++) {
            if (!curr[arr[i]])
                // 没有找到
                return;
            curr = curr[arr[i]];
        }

        return curr[arr[arr.length - 1]];
    },

    /**
     * 浅克隆一个object
     */
    clone : function(target) {
        if (null === target || "object" !== typeof target) return target;
        var obj = new target.constructor();
        for (var attr in target) {
            // 为这个对象添加新的属性
            if (target.hasOwnProperty(attr))
                obj[attr] = target[attr];
        }
        return obj;
    },

    /**
     * 判断是否是一个Array
     */
    isArray : Array.isArray || function(a) { return toString.call(a) === '[object Array]'; },

    /**
     * 删除html元素
     * @param html
     */
    removeHTML: (function() {
        var removingHTML;
        return function(html) {
            if (html && html.parentNode) {
                if (removingHTML === html) {
                    return;
                }
                removingHTML = html;
                html.parentNode.removeChild(html);
                removingHTML = null;
            }
        };
    })(),

    /**
     * 将node元素的Transform更新到html元素上
     * @param node 需要拷贝其Transform信息的节点
     * @param html 需要设置其Transform信息的html元素
     */
    updateTransform: function(node, html){
        var game = node.game;
        var scaleFactor = game.phaser.scale.scaleFactorInversed;
        var canvasRect = game.canvas.getBoundingClientRect();
        var parentRect = game.canvas.parentNode.getBoundingClientRect();
        var offX = canvasRect.left - parentRect.left;
        var offY = canvasRect.top - parentRect.top;
        var style = html.style;
        var rect = node.rect;
        var worldTransform = node.worldTransform;
        var transformOrigin = (-rect.x).toFixed(5) + 'px ' + (-rect.y).toFixed(5) + 'px';
        var transform = 'matrix(' +
                        (worldTransform.a * scaleFactor.x).toFixed(5) + ',' +
                        (worldTransform.b * scaleFactor.x).toFixed(5) + ',' +
                        (worldTransform.c * scaleFactor.y).toFixed(5) + ',' +
                        (worldTransform.d * scaleFactor.y).toFixed(5) + ',' +
                        (worldTransform.tx * scaleFactor.x + offX).toFixed(0) + ',' +
                        (worldTransform.ty * scaleFactor.y + offY).toFixed(0) +
                        ')';

        style.left = (rect.x).toFixed(5) + 'px';
        style.top = (rect.y).toFixed(5) + 'px';
        style.width = (rect.width).toFixed(5) + 'px';
        style.height = (rect.height).toFixed(5) + 'px';

        style.webkitTransform = transform;
        style.mozTransform = transform;
        style.msTransform = transform;
        style.oTransform = transform;
        style.transform = transform;

        style.webkitTransformOrigin = transformOrigin;
        style.mozTransformOrigin = transformOrigin;
        style.msTransformOrigin = transformOrigin;
        style.oTransformOrigin = transformOrigin;
        style.transformOrigin = transformOrigin;
    },

    /**
     * 格式化字符串
     * 使用方式形如：qc.Util.formatString('hello {0}', 'world')
     */
     formatString : function(format) {
        var args = arguments;

        if (args.length <= 1)
            return format;

        return format.replace(/\{(\d+)\}/g,
            function(m, i) {
                return args[parseInt(i) + 1];
            }
        );
     },

    // 报错弹出提示
    popupError : function(errorMessage) {
        if (window.parent && window.parent.G && window.parent.G.notification)
        {
            var G = window.parent.G;
            // 在编辑器中
            var str = qc.Util.formatString(G._('Exception notification'), errorMessage);
            G.notification.error(str)
        }
        else
        {
            var str = 'Error: ' + errorMessage;
            window.alert(str);
        }
    }
};

// 关注未捕获的报错，弹出提示
window.onerror = function( errorMessage, scriptURI, lineNumber, columnNumber, error)
{
    qc.Util.popupError(errorMessage);
}

/**
 * @author luohj
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 颜色的描述
 * 颜色值，可以是：
 * '#FFFFFF' '#ffffff' - 十六进制描述的字符串，不带alpha
 * '#00FFFFFF' '#00ffffff' - 十六进制描述的字符串，带alpha
 * 'RGB(100, 100, 100)' 'rgb(100, 100, 100)' - 字符串描述rgb
 * 'RGBA(100, 100, 100, 0.5)' 'rgba(100, 100, 100, 0.5)' - 字符串描述rgba
 * 0xFFFFFF - int类型描述：rgb
 * 0x00FFFFFF - int类型描述：argb
 * [128, 10, 0] - 数组描述
 * [11, 128, 0, 0.5] - 数组描述
 * @param {string | number | undefined} color
 */
var Color = qc.Color = function(color) {
    this.alpha = 1;
    this._rgb = [0xFF, 0xFF, 0xFF];
    if (color == null) return;

    if (typeof color === 'number') {
        this._rgb[0] = color >> 16 & 0xFF;
        this._rgb[1] = color >> 8 & 0xFF;
        this._rgb[2] = color & 0xFF;

        this.alpha = (color >>> 24) / 255;
    }
    else if (qc.Util.isArray(color)) {
        this.rgb = color;
    }
    else if (typeof color === 'string') {
        color = color.toLowerCase();
        if (color.indexOf('rgb') === 0) {
            var rgba = Phaser.Color.webToColor(color);
        }
        else {
            var rgba = this._hexToColor(color);
            rgba.a = rgba.a / 255;
        }
        this._rgb[0] = rgba.r;
        this._rgb[1] = rgba.g;
        this._rgb[2] = rgba.b;
        this.alpha = rgba.a;
    }
    else
        throw new Error('Invalid color');
};

Color.prototype = {};
Color.prototype.constructor = Color;

Object.defineProperties(Color.prototype, {
    /**
     * @property {number} alpha - 透明度（0 - 1）
     */
    alpha: {
        get: function()  { return this._alpha; },
        set: function(v) {
            if (v < 0 || v > 1) v = 1;
            this._alpha = v;
        }
    },

    /**
     * @property {array} rgb - [r, g, b] r, g, b : 范围为0 - 255
     */
    rgb: {
        get: function() {
            return this._rgb;
        },
        set: function(rgb) {
            if (!qc.Util.isArray(rgb)) throw new Error('Expected:Array');
            if (rgb.length < 3) throw new Error('Invalid rgb');
            this._rgb[0] = rgb[0];
            this._rgb[1] = rgb[1];
            this._rgb[2] = rgb[2];
            if (rgb.length === 4)
                this.alpha = rgb[3];
            else
                this.alpha = 1;
        }
    },

    /**
     * @property {string} class - 类名
     * @readonly
     * @internal
     */
    class : {
        get : function() { return 'qc.Color'; }
    }
});

/**
 * 颜色转换为字符串描述
 * 'rgba'   -> RGBA(100, 100, 100, 0.5)
 * 'rgb'    -> RGB(100, 100, 100)
 * '#rgb'   -> #FFFFFF
 * '#argb'  -> #80FFFFFF
 * @method toString
 * @param {string} patten
 * @return {string}
 */
Color.prototype.toString = function(patten) {
    switch (patten) {
        case '#rgb' :
            return '#' + Phaser.Color.componentToHex(this._rgb[0]) +
                Phaser.Color.componentToHex(this._rgb[1]) +
                Phaser.Color.componentToHex(this._rgb[2]);
        case '#argb' :
            return '#' + Phaser.Color.componentToHex(Math.round(this.alpha * 255)) +
                Phaser.Color.componentToHex(this._rgb[0]) +
                Phaser.Color.componentToHex(this._rgb[1]) +
                Phaser.Color.componentToHex(this._rgb[2]);
        case 'rgb' :
            return 'rgb(' + this._rgb[0].toString() + ',' +
                this._rgb[1].toString() + ',' +
                this._rgb[2].toString() + ')';
        default :
            return 'rgba(' + this._rgb[0].toString() + ',' +
                this._rgb[1].toString() + ',' +
                this._rgb[2].toString() + ',' +
                this.alpha + ')';
    }
};

/**
 * 转为整数描述
 * @method toNumber
 * @param {boolean} alpha - 是否携带alpha值返回，默认为false
 * @return {number}
 */
Color.prototype.toNumber = function(alpha) {
    if (alpha)
        return ((this.alpha * 255) << 24 |
            this._rgb[0] << 16 |
            this._rgb[1]<< 8 |
            this._rgb[2]) >>> 0;

    return (this._rgb[0] << 16 |
        this._rgb[1]<< 8 |
        this._rgb[2]) >>> 0;
};

/**
 * 转换16进制的字符串为rgba
 * @method _hexToColor
 * @param hex - 16进制颜色
 * @returns {object}
 * @private
 */
Color.prototype._hexToColor= function (hex) {
    if (hex.length <= 0 || hex[0] !== '#') return { r : 255, g : 255, b : 255, a : 255 };

    hex = hex.substr(1, hex.length - 1);
    var hasAlpha = false;
    if (hex.length > 6 && hex.length <= 8) {
        hasAlpha = true;
        while (hex.length < 8) hex = '0' + hex;
    }
    while (hex.length < 6) hex = '0' + hex;
    var alpha = 255;
    var i = 0;
    if (hasAlpha) {
        alpha = parseInt(hex.substr(i, 2), 16);
        i += 2;
    }
    var r = parseInt(hex.substr(i, 2), 16);
    i += 2;
    var g = parseInt(hex.substr(i, 2), 16);
    i += 2;
    var b = parseInt(hex.substr(i, 2), 16);

    return { r : r, g : g, b : b, a : alpha };
};

/**
 * 几种常见的颜色
 */
Color.black = new Color(0xff000000);
Color.white = new Color(0xffffffff);
Color.red = new Color(0xffff0000);
Color.yellow = new Color(0xffffff00);
Color.blue = new Color(0xff00ffff);
Color.green = new Color(0xff00ff00);
Color.grey = new Color(0xffcccccc);
Color.shadow = new Color(0x80000000);
Color.background = new Color(0xff474747);

/**
 * @author wudm
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 全局统一的 canvas 缓存处理
 */

qc.CanvasPool = {
    // canvas 池描述
    pool : {},
    unused : [],
    cookie : 19870101,

    // 入口，获取一个闲置的可用的 canvas
    get : function(key) {
        var pool = qc.CanvasPool.pool;
        var canvas = pool[key];
        var isDirty = false;

        if (!canvas) {
            // 无效的 canvas 对象
            isDirty = true;

            // 当前 unused 中是否有元素可用，可用就拿来用，否则抛弃
            var unused = qc.CanvasPool.unused;
            if (unused.length)
                pool[key] = canvas = unused.pop();
            else
                pool[key] = canvas = Phaser.Canvas.create(1, 1);
        }

        // 记录本帧该 canvas 有被调度过
        canvas._cookie = qc.CanvasPool.cookie;
        return { canvas : canvas, dirty : isDirty };
    },

    // 统计池子使用情况，目前主要用于 debug
    stat : function() {
        var count = 0;
        var totalSize = 0;
        var pool = qc.CanvasPool.pool;

        for (var key in pool) {
            var canvas = pool[key];
            if (!canvas) continue;

            count++;
            totalSize += canvas.width * canvas.height;
        }

        console.info('当前Canvas使用中：' + count + '，总像素为：' + totalSize +
            '，未使用的Canvas数量：' + qc.CanvasPool.unused.length);
    },

    // 心跳，驱动回收
    postRender : function() {
        var self = qc.CanvasPool;
        var cookie = self.cookie;
        self.cookie = cookie + 1;

        var pool = self.pool;
        var unused = self.unused;

        // 当帧没有被调度到的 canvas 就直接回收
        // 判定当前是否有被调度的标准是 canvas._cookie 是上次的 render cookie
        var keys = Object.keys(pool);
        for (var i = 0, len = keys.length; i < len; i++) {
            var key = keys[i];
            var canvas = pool[key];
            if (canvas._cookie !== cookie) {
                // 没有用到，释放空间，再回收掉
                delete pool[key];
                canvas.width = 1;
                canvas.height = 1;
                unused.push(canvas);
            }
        }
    }
};
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * hackpp
 * 游戏的主循环，改由qici发起
 */
Phaser.Game.prototype.update = function(time) {
    var t1 = Date.now();
    this._qc.update(time);
    this._qc.debug.total += Date.now() - t1;
};

// hack住设置渲染模式的代码，加入白名单功能
var phaser_setUpRenderer = Phaser.Game.prototype.setUpRenderer;
Phaser.Game.prototype.setUpRenderer = function() {
    if (this.device.webGL && !this.device.desktop && !this.device.iOS) {
        if (qc.isSupportWebGL && !qc.isSupportWebGL(this._qc)) {
            this.device.webGL = false;
        }
    }
    phaser_setUpRenderer.call(this);
};
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * hackpp
 * 覆盖掉原来phaser的帧调度
 */

/**
 * The core preUpdate - as called by World.
 * @method Phaser.Group#preUpdate
 * @protected
 */
Phaser.Group.prototype.preUpdate = function () {
    var qc = this._qc;
    if (qc && !qc.static) {
        if (qc.preUpdate) qc.preUpdate();

        // 脚本调度
        var scripts = qc.scripts;
        var i = scripts.length;
        while (i--) {
            var script = scripts[i];
            if (!script || !script._enable || !script.__hadUpdateOrRender || !script.preUpdate) continue;

            // 如果当前处于editor模式，并且本脚本没有说明是在editor模式下运行，就不要调度了
            if (qc.game.device.editor === true && script.runInEditor !== true) continue;

            // 调度之
            script.preUpdate();

            // 节点在脚本中析构了，就不继续调度了
            if (!this.visible) return;
        }
    }

    var i = this.children.length;
    while (i--)
    {
        if (this.children[i].visible) {
            this.children[i].preUpdate();
        }
        else {
            this.children[i].renderOrderID = -1;
        }
    }

    return true;
};

/**
 * The core update - as called by World.
 * @method Phaser.Group#update
 * @protected
 */
Phaser.Group.prototype.update = function () {
    var qc = this._qc;
    if (qc && !qc.static) {
        if (qc.update) qc.update();

        // 脚本调度
        var scripts = qc.scripts;
        var i = scripts.length;
        while (i--) {
            var script = scripts[i];
            if (!script || !script._enable || !script.__hadUpdateOrRender || !script.update) continue;

            // 如果当前处于editor模式，并且本脚本没有说明是在editor模式下运行，就不要调度了
            if (qc.game.device.editor === true && script.runInEditor !== true) continue;

            // 调度之
            script.update();

            // 节点在脚本中析构了，就不继续调度了
            if (!this.visible) return;
        }
    }

    var i = this.children.length;
    while (i--)
    {
        if (this.children[i].visible)
            this.children[i].update();
    }
};

/**
 * The core postUpdate - as called by World.
 * @method Phaser.Group#postUpdate
 * @protected
 */
Phaser.Group.prototype.postUpdate = function () {
    //  Fixed to Camera?
    if (this.fixedToCamera)
    {
        this.x = this.game.camera.view.x + this.cameraOffset.x;
        this.y = this.game.camera.view.y + this.cameraOffset.y;
    }

    var qc = this._qc;
    if (qc && !qc.static) {
        if (qc.postUpdate) qc.postUpdate();

        // 脚本调度
        var scripts = qc.scripts;
        var i = scripts.length;
        while (i--) {
            var script = scripts[i];
            if (!script || !script._enable || !script.__hadUpdateOrRender || !script.postUpdate) continue;

            // 如果当前处于editor模式，并且本脚本没有说明是在editor模式下运行，就不要调度了
            if (qc.game.device.editor === true && script.runInEditor !== true) continue;

            // 调度之
            script.postUpdate();

            // 节点在脚本中析构了，就不继续调度了
            if (!this.visible) return;
        }
    }

    var i = this.children.length;
    while (i--)
    {
        if (this.children[i].visible)
            this.children[i].postUpdate();
    }
};

/**
 * @author chenx
 * @date 2015.11.9
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * @hackpp 替换掉场景加载完毕的判定，需要等待资源解析完毕以后才能认为加载成功了
 */
Phaser.StateManager.prototype.loadComplete = function () {
    var self = this;
    if (self._created === true) return;
    var game = self.game._qc;

    if (game.assets.parsing) {
        game.timer.add(15, function() {
            self.loadComplete();
        })
        return;
    }

    if (self._created === false && self.onCreateCallback)
    {
        if (game.state.loadingAnimation) {
            game.state.delayCreateCallback = function() {
                self._created = true;
                self.onCreateCallback.call(self.callbackContext, self.game);                
            }
        }else {
            self._created = true;
            self.onCreateCallback.call(self.callbackContext, self.game);            
        }
    }
    else
    {
        self._created = true;
    }
};

/**
 * @author linyw
 * copyright 2015 Qcplay All Rights Reserved.
 */


var oldDeviceInitialize = Phaser.Device._initialize;

Phaser.Device._initialize = function () {
    oldDeviceInitialize.call(this);
    
    var ua = window.navigator.userAgent;
    
    // 判断是否为UCBrowser浏览器
    this.UCBrowser = /UCBrowser/.test(ua);

    // 判断iOS版本号
    if (this.iOS)
    {
        (navigator.appVersion).match(/OS (\d+)/);
        this.iOSVersion = parseInt(RegExp.$1, 10);
    }

    // 获取AppleWebkit类型和版本号
    var appleWebKit = /AppleWebKit\/([0-9\.]+)/;
    var result = appleWebKit.exec(navigator.userAgent);
    if (result && result.length > 0) {
        this.AppleWebKit = true;
        this.AppleWebKitVersion = result[1].split('.');
    }
    else {
        this.AppleWebKit = false;
    }

    this.supportStencil = !this.AppleWebKit || this.AppleWebKitVersion[0] > 534;
    
    if (this.android && this.UCBrowser) {
        qc.__IS_ANDROID_UC = true;
    }
};
/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2015 Photon Storm Ltd.
 * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
 */

/**
 * An Animation instance contains a single animation and the controls to play it.
 *
 * It is created by the AnimationManager, consists of Animation.Frame objects and belongs to a single Game Object such as a Sprite.
 *
 * @class Phaser.Animation
 * @constructor
 * @param {Phaser.Game} game - A reference to the currently running game.
 * @param {Phaser.Sprite} parent - A reference to the owner of this Animation.
 * @param {string} name - The unique name for this animation, used in playback commands.
 * @param {Phaser.FrameData} frameData - The FrameData object that contains all frames used by this Animation.
 * @param {number[]|string[]} frames - An array of numbers or strings indicating which frames to play in which order.
 * @param {number} [frameRate=60] - The speed at which the animation should play. The speed is given in frames per second.
 * @param {boolean} [loop=false] - Whether or not the animation is looped or just plays once.
 * @param {boolean} loop - Should this animation loop when it reaches the end or play through once.
 */
Phaser.Animation = function (game, parent, name, frameData, frames, frameRate, loop) {

    if (typeof loop === 'undefined') { loop = false; }

    /**
     * @property {Phaser.Game} game - A reference to the currently running Game.
     */
    this.game = game;

    /**
     * @property {Phaser.Sprite} _parent - A reference to the parent Sprite that owns this Animation.
     * @private
     */
    this._parent = parent;

    /**
     * @property {Phaser.FrameData} _frameData - The FrameData the Animation uses.
     * @private
     */
    this._frameData = frameData;

    /**
     * @property {string} name - The user defined name given to this Animation.
     */
    this.name = name;

    /**
     * @property {array} _frames
     * @private
     */
    this._frames = [];
    this._frames = this._frames.concat(frames);

    /**
     * @property {number} delay - The delay in ms between each frame of the Animation, based on the given frameRate.
     */
    this.delay = 1000 / frameRate;

    /**
     * @property {boolean} loop - The loop state of the Animation.
     */
    this.loop = loop;

    /**
     * @property {number} loopCount - The number of times the animation has looped since it was last started.
     */
    this.loopCount = 0;

    /**
     * @property {boolean} killOnComplete - Should the parent of this Animation be killed when the animation completes?
     * @default
     */
    this.killOnComplete = false;

    /**
     * @property {boolean} isFinished - The finished state of the Animation. Set to true once playback completes, false during playback.
     * @default
     */
    this.isFinished = false;

    /**
     * @property {boolean} isPlaying - The playing state of the Animation. Set to false once playback completes, true during playback.
     * @default
     */
    this.isPlaying = false;

    /**
     * @property {boolean} isPaused - The paused state of the Animation.
     * @default
     */
    this.isPaused = false;

    /**
     * @property {boolean} _pauseStartTime - The time the animation paused.
     * @private
     * @default
     */
    this._pauseStartTime = 0;

    /**
     * @property {number} _frameIndex
     * @private
     * @default
     */
    this._frameIndex = 0;

    /**
     * @property {number} _frameDiff
     * @private
     * @default
     */
    this._frameDiff = 0;

    /**
     * @property {number} _frameSkip
     * @private
     * @default
     */
    this._frameSkip = 1;

    /**
     * @property {Phaser.Frame} currentFrame - The currently displayed frame of the Animation.
     */
    this.currentFrame = this._frameData ? this._frameData.getFrame(this._frames[this._frameIndex]) : this._frameIndex;

    /**
     * @property {Phaser.Signal} onStart - This event is dispatched when this Animation starts playback.
     */
    this.onStart = new Phaser.Signal();

    /**
     * @property {Phaser.Signal|null} onUpdate - This event is dispatched when the Animation changes frame. By default this event is disabled due to its intensive nature. Enable it with: `Animation.enableUpdate = true`.
     * @default
     */
    this.onUpdate = null;

    /**
     * @property {Phaser.Signal} onComplete - This event is dispatched when this Animation completes playback. If the animation is set to loop this is never fired, listen for onAnimationLoop instead.
     */
    this.onComplete = new Phaser.Signal();

    /**
     * @property {Phaser.Signal} onLoop - This event is dispatched when this Animation loops.
     */
    this.onLoop = new Phaser.Signal();

    //  Set-up some event listeners
    this.game.onPause.add(this.onPause, this);
    this.game.onResume.add(this.onResume, this);

};

Phaser.Animation.prototype = {

    /**
     * Plays this animation.
     *
     * @method Phaser.Animation#play
     * @param {number} [frameRate=null] - The framerate to play the animation at. The speed is given in frames per second. If not provided the previously set frameRate of the Animation is used.
     * @param {boolean} [loop=false] - Should the animation be looped after playback. If not provided the previously set loop value of the Animation is used.
     * @param {boolean} [killOnComplete=false] - If set to true when the animation completes (only happens if loop=false) the parent Sprite will be killed.
     * @return {Phaser.Animation} - A reference to this Animation instance.
     */
    play: function (frameRate, loop, killOnComplete) {

        if (typeof frameRate === 'number')
        {
            //  If they set a new frame rate then use it, otherwise use the one set on creation
            this.delay = 1000 / frameRate;
        }

        if (typeof loop === 'boolean')
        {
            //  If they set a new loop value then use it, otherwise use the one set on creation
            this.loop = loop;
        }

        if (typeof killOnComplete !== 'undefined')
        {
            //  Remove the parent sprite once the animation has finished?
            this.killOnComplete = killOnComplete;
        }

        this.isPlaying = true;
        this.isFinished = false;
        this.paused = false;
        this.loopCount = 0;

        this._timeLastFrame = this.game.time.time;
        this._timeNextFrame = this.game.time.time + this.delay;

        this._frameIndex = 0;
        this.updateCurrentFrame(false);

        this._parent.events.onAnimationStart$dispatch(this._parent, this);

        this.onStart.dispatch(this._parent, this);

        this._parent.animations.currentAnim = this;
        this._parent.animations.currentFrame = this.currentFrame;

        return this;

    },

    /**
     * Sets this animation back to the first frame and restarts the animation.
     *
     * @method Phaser.Animation#restart
     */
    restart: function () {

        this.isPlaying = true;
        this.isFinished = false;
        this.paused = false;
        this.loopCount = 0;

        this._timeLastFrame = this.game.time.time;
        this._timeNextFrame = this.game.time.time + this.delay;

        this._frameIndex = 0;

        this.currentFrame = this._frameData ? this._frameData.getFrame(this._frames[this._frameIndex]) : this._frameIndex;

        this._parent.setFrame(this.currentFrame);

        this._parent.animations.currentAnim = this;
        this._parent.animations.currentFrame = this.currentFrame;

        this.onStart.dispatch(this._parent, this);

    },

    /**
     * Sets this animations playback to a given frame with the given ID.
     *
     * @method Phaser.Animation#setFrame
     * @param {string|number} [frameId] - The identifier of the frame to set. Can be the name of the frame, the sprite index of the frame, or the animation-local frame index.
     * @param {boolean} [useLocalFrameIndex=false] - If you provide a number for frameId, should it use the numeric indexes of the frameData, or the 0-indexed frame index local to the animation.
     */
    setFrame: function(frameId, useLocalFrameIndex) {

        var frameIndex;

        if (typeof useLocalFrameIndex === 'undefined')
        {
            useLocalFrameIndex = false;
        }

        //  Find the index to the desired frame.
        if (typeof frameId === "string")
        {
            for (var i = 0; i < this._frames.length; i++)
            {
                if (this._frameData && this._frameData.getFrame(this._frames[i]).name === frameId)
                {
                    frameIndex = i;
                }
            }
        }
        else if (typeof frameId === "number")
        {
            if (useLocalFrameIndex)
            {
                frameIndex = frameId;
            }
            else
            {
                for (var i = 0; i < this._frames.length; i++)
                {
                    if (this._frames[i] === frameIndex)
                    {
                        frameIndex = i;
                    }
                }
            }
        }

        if (frameIndex)
        {
            //  Set the current frame index to the found index. Subtract 1 so that it animates to the desired frame on update.
            this._frameIndex = frameIndex - 1;

            //  Make the animation update at next update
            this._timeNextFrame = this.game.time.time;

            this.update();
        }

    },

    /**
     * Stops playback of this animation and set it to a finished state. If a resetFrame is provided it will stop playback and set frame to the first in the animation.
     * If `dispatchComplete` is true it will dispatch the complete events, otherwise they'll be ignored.
     *
     * @method Phaser.Animation#stop
     * @param {boolean} [resetFrame=false] - If true after the animation stops the currentFrame value will be set to the first frame in this animation.
     * @param {boolean} [dispatchComplete=false] - Dispatch the Animation.onComplete and parent.onAnimationComplete events?
     */
    stop: function (resetFrame, dispatchComplete) {

        if (typeof resetFrame === 'undefined') { resetFrame = false; }
        if (typeof dispatchComplete === 'undefined') { dispatchComplete = false; }

        this.isPlaying = false;
        this.isFinished = true;
        this.paused = false;

        if (resetFrame)
        {
            this.currentFrame = this._frameData ? this._frameData.getFrame(this._frames[0]) : 0;

            this._parent.setFrame(this.currentFrame);
        }

        if (dispatchComplete)
        {
            this._parent.events.onAnimationComplete$dispatch(this._parent, this);
            this.onComplete.dispatch(this._parent, this);
        }

    },

    /**
     * Called when the Game enters a paused state.
     *
     * @method Phaser.Animation#onPause
     */
    onPause: function () {

        if (this.isPlaying)
        {
            this._frameDiff = this._timeNextFrame - this.game.time.time;
        }

    },

    /**
     * Called when the Game resumes from a paused state.
     *
     * @method Phaser.Animation#onResume
     */
    onResume: function () {

        if (this.isPlaying)
        {
            this._timeNextFrame = this.game.time.time + this._frameDiff;
        }

    },

    /**
     * Updates this animation. Called automatically by the AnimationManager.
     *
     * @method Phaser.Animation#update
     */
    update: function () {

        if (this.isPaused)
        {
            return false;
        }

        if (this.isPlaying && this.game.time.time >= this._timeNextFrame)
        {
            this._frameSkip = 1;

            //  Lagging?
            this._frameDiff = this.game.time.time - this._timeNextFrame;

            this._timeLastFrame = this.game.time.time;

            if (this._frameDiff > this.delay)
            {
                //  We need to skip a frame, work out how many
                this._frameSkip = Math.floor(this._frameDiff / this.delay);
                this._frameDiff -= (this._frameSkip * this.delay);
                this._frameSkip += 1;
            }

            //  And what's left now?
            this._timeNextFrame = this.game.time.time + (this.delay - this._frameDiff);

            this._frameIndex += this._frameSkip;

            if (this._frameIndex >= this._frames.length)
            {
                if (this.loop)
                {
                    // Update current state before event callback
                    this._frameIndex %= this._frames.length;
                    this.currentFrame = this._frameData ? this._frameData.getFrame(this._frames[this._frameIndex]) : this._frameIndex;
                    this.loopCount++;
                    this._parent.events.onAnimationLoop$dispatch(this._parent, this);
                    this.onLoop.dispatch(this._parent, this);
                    return this.updateCurrentFrame(true);
                }
                else
                {
                    this.complete();
                    return false;
                }
            }
            else
            {
                return this.updateCurrentFrame(true);
            }
        }

        return false;

    },

    /**
     * Changes the currentFrame per the _frameIndex, updates the display state,
     * and triggers the update signal.
     *
     * Returns true if the current frame update was 'successful', false otherwise.
     *
     * @method Phaser.Animation#updateCurrentFrame
     * @param {bool} signalUpdate - If true th onUpdate signal will be triggered.
     * @private
     */
    updateCurrentFrame: function (signalUpdate) {

        if (!this._frameData)
        {
            // The animation is already destroyed, probably from a callback
            return false;
        }

        this.currentFrame = this._frameData.getFrame(this._frames[this._frameIndex]);

        if (this.currentFrame)
        {
            this._parent.setFrame(this.currentFrame);

            if (this._parent.__tilePattern)
            {
                this._parent.__tilePattern = false;
                this._parent.tilingTexture = false;
            }
        }

        if (this.onUpdate && signalUpdate)
        {
            this.onUpdate.dispatch(this, this.currentFrame);
            // False if the animation was destroyed from within a callback
            return !!this._frameData;
        }
        else
        {
            return true;
        }

    },

    /**
     * Advances by the given number of frames in the Animation, taking the loop value into consideration.
     *
     * @method Phaser.Animation#next
     * @param {number} [quantity=1] - The number of frames to advance.
     */
    next: function (quantity) {

        if (typeof quantity === 'undefined') { quantity = 1; }

        var frame = this._frameIndex + quantity;

        if (frame >= this._frames.length)
        {
            if (this.loop)
            {
                frame %= this._frames.length;
            }
            else
            {
                frame = this._frames.length - 1;
            }
        }

        if (frame !== this._frameIndex)
        {
            this._frameIndex = frame;
            this.updateCurrentFrame(true);
        }

    },

    /**
     * Moves backwards the given number of frames in the Animation, taking the loop value into consideration.
     *
     * @method Phaser.Animation#previous
     * @param {number} [quantity=1] - The number of frames to move back.
     */
    previous: function (quantity) {

        if (typeof quantity === 'undefined') { quantity = 1; }

        var frame = this._frameIndex - quantity;

        if (frame < 0)
        {
            if (this.loop)
            {
                frame = this._frames.length + frame;
            }
            else
            {
                frame++;
            }
        }

        if (frame !== this._frameIndex)
        {
            this._frameIndex = frame;
            this.updateCurrentFrame(true);
        }

    },

    /**
     * Changes the FrameData object this Animation is using.
     *
     * @method Phaser.Animation#updateFrameData
     * @param {Phaser.FrameData} frameData - The FrameData object that contains all frames used by this Animation.
     */
    updateFrameData: function (frameData) {

        this._frameData = frameData;
        this.currentFrame = this._frameData ? this._frameData.getFrame(this._frames[this._frameIndex % this._frames.length]) : this._frameIndex % this._frames.length;
    },

    /**
     * Cleans up this animation ready for deletion. Nulls all values and references.
     *
     * @method Phaser.Animation#destroy
     */
    destroy: function () {

        if (!this.game)
        {
            // Already destroyed
            return;
        }

        this.game.onPause.remove(this.onPause, this);
        this.game.onResume.remove(this.onResume, this);

        this.game = null;
        this._parent = null;
        this._frames = null;
        this._frameData = null;
        this.currentFrame = null;
        this.isPlaying = false;

        this.onStart.dispose();
        this.onLoop.dispose();
        this.onComplete.dispose();

        if (this.onUpdate)
        {
            this.onUpdate.dispose();
        }

    },

    /**
     * Called internally when the animation finishes playback.
     * Sets the isPlaying and isFinished states and dispatches the onAnimationComplete event if it exists on the parent and local onComplete event.
     *
     * @method Phaser.Animation#complete
     */
    complete: function () {

        this._frameIndex = this._frames.length - 1;
        this.currentFrame = this._frameData ? this._frameData.getFrame(this._frames[this._frameIndex]) : this._frameIndex;

        this.isPlaying = false;
        this.isFinished = true;
        this.paused = false;

        this._parent.events.onAnimationComplete$dispatch(this._parent, this);

        this.onComplete.dispatch(this._parent, this);

        if (this.killOnComplete)
        {
            this._parent.kill();
        }

    }

};

Phaser.Animation.prototype.constructor = Phaser.Animation;

/**
 * @name Phaser.Animation#paused
 * @property {boolean} paused - Gets and sets the paused state of this Animation.
 */
Object.defineProperty(Phaser.Animation.prototype, 'paused', {

    get: function () {

        return this.isPaused;

    },

    set: function (value) {

        this.isPaused = value;

        if (value)
        {
            //  Paused
            this._pauseStartTime = this.game.time.time;
        }
        else
        {
            //  Un-paused
            if (this.isPlaying)
            {
                this._timeNextFrame = this.game.time.time + this.delay;
            }
        }

    }

});

/**
 * @name Phaser.Animation#frameTotal
 * @property {number} frameTotal - The total number of frames in the currently loaded FrameData, or -1 if no FrameData is loaded.
 * @readonly
 */
Object.defineProperty(Phaser.Animation.prototype, 'frameTotal', {

    get: function () {
        return this._frames.length;
    }

});

/**
 * @name Phaser.Animation#frame
 * @property {number} frame - Gets or sets the current frame index and updates the Texture Cache for display.
 */
Object.defineProperty(Phaser.Animation.prototype, 'frame', {

    get: function () {

        if (this.currentFrame !== null)
        {
            return this.currentFrame.index;
        }
        else
        {
            return this._frameIndex;
        }

    },

    set: function (value) {

        this.currentFrame = this._frameData ? this._frameData.getFrame(this._frames[value]) : value;

        if (this.currentFrame !== null)
        {
            this._frameIndex = value;
            this._parent.setFrame(this.currentFrame);

            if (this.onUpdate)
            {
                this.onUpdate.dispatch(this, this.currentFrame);
            }
        }

    }

});

/**
 * @name Phaser.Animation#speed
 * @property {number} speed - Gets or sets the current speed of the animation in frames per second. Changing this in a playing animation will take effect from the next frame. Minimum value is 1.
 */
Object.defineProperty(Phaser.Animation.prototype, 'speed', {

    get: function () {

        return Math.round(1000 / this.delay);

    },

    set: function (value) {

        if (value >= 1)
        {
            this.delay = 1000 / value;
        }

    }

});

/**
 * @name Phaser.Animation#enableUpdate
 * @property {boolean} enableUpdate - Gets or sets if this animation will dispatch the onUpdate events upon changing frame.
 */
Object.defineProperty(Phaser.Animation.prototype, 'enableUpdate', {

    get: function () {

        return (this.onUpdate !== null);

    },

    set: function (value) {

        if (value && this.onUpdate === null)
        {
            this.onUpdate = new Phaser.Signal();
        }
        else if (!value && this.onUpdate !== null)
        {
            this.onUpdate.dispose();
            this.onUpdate = null;
        }

    }

});

/**
 * Really handy function for when you are creating arrays of animation data but it's using frame names and not numbers.
 * For example imagine you've got 30 frames named: 'explosion_0001-large' to 'explosion_0030-large'
 * You could use this function to generate those by doing: Phaser.Animation.generateFrameNames('explosion_', 1, 30, '-large', 4);
 *
 * @method Phaser.Animation.generateFrameNames
 * @static
 * @param {string} prefix - The start of the filename. If the filename was 'explosion_0001-large' the prefix would be 'explosion_'.
 * @param {number} start - The number to start sequentially counting from. If your frames are named 'explosion_0001' to 'explosion_0034' the start is 1.
 * @param {number} stop - The number to count to. If your frames are named 'explosion_0001' to 'explosion_0034' the stop value is 34.
 * @param {string} [suffix=''] - The end of the filename. If the filename was 'explosion_0001-large' the prefix would be '-large'.
 * @param {number} [zeroPad=0] - The number of zeroes to pad the min and max values with. If your frames are named 'explosion_0001' to 'explosion_0034' then the zeroPad is 4.
 * @return {string[]} An array of framenames.
 */
Phaser.Animation.generateFrameNames = function (prefix, start, stop, suffix, zeroPad) {

    if (typeof suffix === 'undefined') { suffix = ''; }

    var output = [];
    var frame = '';

    if (start < stop)
    {
        for (var i = start; i <= stop; i++)
        {
            if (typeof zeroPad === 'number')
            {
                //  str, len, pad, dir
                frame = Phaser.Utils.pad(i.toString(), zeroPad, '0', 1);
            }
            else
            {
                frame = i.toString();
            }

            frame = prefix + frame + suffix;

            output.push(frame);
        }
    }
    else
    {
        for (var i = start; i >= stop; i--)
        {
            if (typeof zeroPad === 'number')
            {
                //  str, len, pad, dir
                frame = Phaser.Utils.pad(i.toString(), zeroPad, '0', 1);
            }
            else
            {
                frame = i.toString();
            }

            frame = prefix + frame + suffix;

            output.push(frame);
        }
    }

    return output;

};

/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2015 Photon Storm Ltd.
 * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
 */

/**
 * The Animation Manager is used to add, play and update Phaser Animations.
 * Any Game Object such as Phaser.Sprite that supports animation contains a single AnimationManager instance.
 *
 * @class Phaser.AnimationManager
 * @constructor
 * @param {Phaser.Sprite} sprite - A reference to the Game Object that owns this AnimationManager.
 */
Phaser.AnimationManager = function (sprite) {

    /**
     * @property {Phaser.Sprite} sprite - A reference to the parent Sprite that owns this AnimationManager.
     */
    this.sprite = sprite;

    /**
     * @property {Phaser.Game} game - A reference to the currently running Game.
     */
    this.game = sprite.game;

    /**
     * @property {Phaser.Frame} currentFrame - The currently displayed Frame of animation, if any.
     * @default
     */
    this.currentFrame = null;

    /**
     * @property {Phaser.Animation} currentAnim - The currently displayed animation, if any.
     * @default
     */
    this.currentAnim = null;

    /**
     * @property {boolean} updateIfVisible - Should the animation data continue to update even if the Sprite.visible is set to false.
     * @default
     */
    this.updateIfVisible = true;

    /**
     * @property {boolean} isLoaded - Set to true once animation data has been loaded.
     * @default
     */
    this.isLoaded = false;

    /**
     * @property {Phaser.FrameData} _frameData - A temp. var for holding the currently playing Animations FrameData.
     * @private
     * @default
     */
    this._frameData = null;

    /**
     * @property {object} _anims - An internal object that stores all of the Animation instances.
     * @private
     */
    this._anims = {};

    /**
     * @property {object} _outputFrames - An internal object to help avoid gc.
     * @private
     */
    this._outputFrames = [];

};

Phaser.AnimationManager.prototype = {

    /**
     * Loads FrameData into the internal temporary vars and resets the frame index to zero.
     * This is called automatically when a new Sprite is created.
     *
     * @method Phaser.AnimationManager#loadFrameData
     * @private
     * @param {Phaser.FrameData} frameData - The FrameData set to load.
     * @param {string|number} frame - The frame to default to.
     * @return {boolean} Returns `true` if the frame data was loaded successfully, otherwise `false`
     */
    loadFrameData: function (frameData, frame) {

        if (typeof frameData === 'undefined')
        {
            return false;
        }

        if (this.isLoaded)
        {
            //   We need to update the frameData that the animations are using
            for (var anim in this._anims)
            {
                this._anims[anim].updateFrameData(frameData);
            }
        }

        this._frameData = frameData;

        if (typeof frame === 'undefined' || frame === null)
        {
            this.frame = 0;
        }
        else
        {
            if (typeof frame === 'string')
            {
                this.frameName = frame;
            }
            else
            {
                this.frame = frame;
            }
        }

        this.isLoaded = true;

        return true;
    },

    /**
     * Loads FrameData into the internal temporary vars and resets the frame index to zero.
     * This is called automatically when a new Sprite is created.
     *
     * @method Phaser.AnimationManager#copyFrameData
     * @private
     * @param {Phaser.FrameData} frameData - The FrameData set to load.
     * @param {string|number} frame - The frame to default to.
     * @return {boolean} Returns `true` if the frame data was loaded successfully, otherwise `false`
     */
    copyFrameData: function (frameData, frame) {

        this._frameData = frameData.clone();

        if (this.isLoaded)
        {
            //   We need to update the frameData that the animations are using
            for (var anim in this._anims)
            {
                this._anims[anim].updateFrameData(this._frameData);
            }
        }

        if (typeof frame === 'undefined' || frame === null)
        {
            this.frame = 0;
        }
        else
        {
            if (typeof frame === 'string')
            {
                this.frameName = frame;
            }
            else
            {
                this.frame = frame;
            }
        }

        this.isLoaded = true;

        return true;
    },

    /**
     * Adds a new animation under the given key. Optionally set the frames, frame rate and loop.
     * Animations added in this way are played back with the play function.
     *
     * @method Phaser.AnimationManager#add
     * @param {string} name - The unique (within this Sprite) name for the animation, i.e. "run", "fire", "walk".
     * @param {Array} [frames=null] - An array of numbers/strings that correspond to the frames to add to this animation and in which order. e.g. [1, 2, 3] or ['run0', 'run1', run2]). If null then all frames will be used.
     * @param {number} [frameRate=60] - The speed at which the animation should play. The speed is given in frames per second.
     * @param {boolean} [loop=false] - Whether or not the animation is looped or just plays once.
     * @param {boolean} [useNumericIndex=true] - Are the given frames using numeric indexes (default) or strings?
     * @return {Phaser.Animation} The Animation object that was created.
     */
    add: function (name, frames, frameRate, loop, useNumericIndex) {

        frames = frames || [];
        frameRate = frameRate || 60;

        if (typeof loop === 'undefined') { loop = false; }

        //  If they didn't set the useNumericIndex then let's at least try and guess it
        if (typeof useNumericIndex === 'undefined')
        {
            if (frames && typeof frames[0] === 'number')
            {
                useNumericIndex = true;
            }
            else
            {
                useNumericIndex = false;
            }
        }

        this._outputFrames.length = 0;

        if (this.sprite._qc && this.sprite._qc.animationType === qc.Sprite.FRAME_ANIMATION) {
            this._frameData.getFrameIndexes(frames, useNumericIndex, this._outputFrames);
            this._anims[name] = new Phaser.Animation(this.game, this.sprite, name, this._frameData, this._outputFrames, frameRate, loop);
        } else {
            var frameLen = this.sprite._qc._animation[name].last;
            var frameAddon = new Array(frameLen);
            for (var i = 0, length = frameLen; i < length; i++) frameAddon[i] = i;
            this._anims[name] = new Phaser.Animation(this.game, this.sprite, name, null, frameAddon, frameRate, loop);
        }

        this.currentAnim = this._anims[name];

        if (this.sprite._qc && this.sprite._qc.animationType === qc.Sprite.FRAME_ANIMATION) this.currentFrame = this.currentAnim.currentFrame;

        // this.sprite.setFrame(this.currentFrame);

        //  CHECK WE STILL NEED THIS - PRETTY SURE IT DOESN'T ACTUALLY DO ANYTHING!
        if (this.sprite.__tilePattern)
        {
            // this.__tilePattern = false;
            this.sprite.__tilePattern = false;
            this.tilingTexture = false;
        }

        return this._anims[name];

    },

    /**
     * Check whether the frames in the given array are valid and exist.
     *
     * @method Phaser.AnimationManager#validateFrames
     * @param {Array} frames - An array of frames to be validated.
     * @param {boolean} [useNumericIndex=true] - Validate the frames based on their numeric index (true) or string index (false)
     * @return {boolean} True if all given Frames are valid, otherwise false.
     */
    validateFrames: function (frames, useNumericIndex) {

        if (typeof useNumericIndex === 'undefined') { useNumericIndex = true; }

        for (var i = 0; i < frames.length; i++)
        {
            if (useNumericIndex === true)
            {
                if (frames[i] > this._frameData.total)
                {
                    return false;
                }
            }
            else
            {
                if (this._frameData.checkFrameName(frames[i]) === false)
                {
                    return false;
                }
            }
        }

        return true;

    },

    /**
     * Play an animation based on the given key. The animation should previously have been added via `animations.add`
     *
     * If the requested animation is already playing this request will be ignored.
     * If you need to reset an already running animation do so directly on the Animation object itself.
     *
     * @method Phaser.AnimationManager#play
     * @param {string} name - The name of the animation to be played, e.g. "fire", "walk", "jump".
     * @param {number} [frameRate=null] - The framerate to play the animation at. The speed is given in frames per second. If not provided the previously set frameRate of the Animation is used.
     * @param {boolean} [loop=false] - Should the animation be looped after playback. If not provided the previously set loop value of the Animation is used.
     * @param {boolean} [killOnComplete=false] - If set to true when the animation completes (only happens if loop=false) the parent Sprite will be killed.
     * @return {Phaser.Animation} A reference to playing Animation instance.
     */
    play: function (name, frameRate, loop, killOnComplete) {

        if (this._anims[name])
        {
            var currentAnim = this.currentAnim;

            if (currentAnim === this._anims[name])
            {
                if (currentAnim.isPlaying === false)
                {
                    currentAnim.paused = false;
                    return currentAnim.play(frameRate, loop, killOnComplete);
                }

                // 还需要界定 frameRate、loop 是否匹配
                if (typeof frameRate === 'number' &&
                    currentAnim.delay !== 1000 / frameRate) {
                    currentAnim.delay = 1000 / frameRate;
                    currentAnim._timeNextFrame = currentAnim._timeLastFrame + currentAnim.delay;
                }

                if (typeof loop === 'boolean') currentAnim.loop = loop;
                if (typeof killOnComplete !== 'undefined') currentAnim.killOnComplete = killOnComplete;

                // 跟之前是匹配的，直接返回
                return currentAnim;
            }
            else
            {
                if (currentAnim && currentAnim.isPlaying)
                {
                    currentAnim.stop();
                }

                currentAnim = this._anims[name];
                currentAnim.paused = false;
                if (this.sprite._qc && this.sprite._qc.animationType === qc.Sprite.FRAME_ANIMATION) this.currentFrame = currentAnim.currentFrame;
                return currentAnim.play(frameRate, loop, killOnComplete);
            }
        }

    },

    /**
     * Stop playback of an animation. If a name is given that specific animation is stopped, otherwise the current animation is stopped.
     * The currentAnim property of the AnimationManager is automatically set to the animation given.
     *
     * @method Phaser.AnimationManager#stop
     * @param {string} [name=null] - The name of the animation to be stopped, e.g. "fire". If none is given the currently running animation is stopped.
     * @param {boolean} [resetFrame=false] - When the animation is stopped should the currentFrame be set to the first frame of the animation (true) or paused on the last frame displayed (false)
     */
    stop: function (name, resetFrame) {

        if (typeof resetFrame === 'undefined') { resetFrame = false; }

        if (typeof name === 'string')
        {
            if (this._anims[name])
            {
                this.currentAnim = this._anims[name];
                this.currentAnim.stop(resetFrame);
            }
        }
        else
        {
            if (this.currentAnim)
            {
                this.currentAnim.stop(resetFrame);
            }
        }

    },

    /**
     * The main update function is called by the Sprites update loop. It's responsible for updating animation frames and firing related events.
     *
     * @method Phaser.AnimationManager#update
     * @protected
     * @return {boolean} True if a new animation frame has been set, otherwise false.
     */
    update: function () {

        if (this.updateIfVisible && !this.sprite.visible)
        {
            return false;
        }

        if (this.currentAnim && this.currentAnim.update())
        {
            if (this.sprite._qc && this.sprite._qc.animationType === qc.Sprite.FRAME_ANIMATION) this.currentFrame = this.currentAnim.currentFrame;
            return true;
        }

        return false;

    },

    /**
     * Advances by the given number of frames in the current animation, taking the loop value into consideration.
     *
     * @method Phaser.AnimationManager#next
     * @param {number} [quantity=1] - The number of frames to advance.
     */
    next: function (quantity) {

        if (this.currentAnim)
        {
            this.currentAnim.next(quantity);
            if (this.sprite._qc && this.sprite._qc.animationType === qc.Sprite.FRAME_ANIMATION) this.currentFrame = this.currentAnim.currentFrame;
        }

    },

    /**
     * Moves backwards the given number of frames in the current animation, taking the loop value into consideration.
     *
     * @method Phaser.AnimationManager#previous
     * @param {number} [quantity=1] - The number of frames to move back.
     */
    previous: function (quantity) {

        if (this.currentAnim)
        {
            this.currentAnim.previous(quantity);
            if (this.sprite._qc && this.sprite._qc.animationType === qc.Sprite.FRAME_ANIMATION) this.currentFrame = this.currentAnim.currentFrame;
        }

    },

    /**
     * Returns an animation that was previously added by name.
     *
     * @method Phaser.AnimationManager#getAnimation
     * @param {string} name - The name of the animation to be returned, e.g. "fire".
     * @return {Phaser.Animation} The Animation instance, if found, otherwise null.
     */
    getAnimation: function (name) {

        if (typeof name === 'string')
        {
            if (this._anims[name])
            {
                return this._anims[name];
            }
        }

        return null;

    },

    /**
     * Refreshes the current frame data back to the parent Sprite and also resets the texture data.
     *
     * @method Phaser.AnimationManager#refreshFrame
     */
    refreshFrame: function () {

        this.sprite.setTexture(PIXI.TextureCache[this.currentFrame.uuid]);

        if (this.sprite.__tilePattern)
        {
            this.__tilePattern = false;
            this.tilingTexture = false;
        }

    },

    /**
     * Destroys all references this AnimationManager contains.
     * Iterates through the list of animations stored in this manager and calls destroy on each of them.
     *
     * @method Phaser.AnimationManager#destroy
     */
    destroy: function () {

        var anim = null;

        for (var anim in this._anims)
        {
            if (this._anims.hasOwnProperty(anim))
            {
                this._anims[anim].destroy();
            }
        }

        this._anims = {};
        this._outputFrames = [];
        this._frameData = null;
        this.currentAnim = null;
        this.currentFrame = null;
        this.sprite = null;
        this.game = null;

    }

};

Phaser.AnimationManager.prototype.constructor = Phaser.AnimationManager;

/**
 * @name Phaser.AnimationManager#frameData
 * @property {Phaser.FrameData} frameData - The current animations FrameData.
 * @readonly
 */
Object.defineProperty(Phaser.AnimationManager.prototype, 'frameData', {

    get: function () {
        return this._frameData;
    }

});

/**
 * @name Phaser.AnimationManager#frameTotal
 * @property {number} frameTotal - The total number of frames in the currently loaded FrameData, or -1 if no FrameData is loaded.
 * @readonly
 */
Object.defineProperty(Phaser.AnimationManager.prototype, 'frameTotal', {

    get: function () {

        return this._frameData.total;
    }

});

/**
 * @name Phaser.AnimationManager#paused
 * @property {boolean} paused - Gets and sets the paused state of the current animation.
 */
Object.defineProperty(Phaser.AnimationManager.prototype, 'paused', {

    get: function () {

        return this.currentAnim.isPaused;

    },

    set: function (value) {

        this.currentAnim.paused = value;

    }

});

/**
 * @name Phaser.AnimationManager#name
 * @property {string} name - Gets the current animation name, if set.
 */
Object.defineProperty(Phaser.AnimationManager.prototype, 'name', {

    get: function () {

        if (this.currentAnim)
        {
            return this.currentAnim.name;
        }

    }

});

/**
 * @name Phaser.AnimationManager#frame
 * @property {number} frame - Gets or sets the current frame index and updates the Texture Cache for display.
 */
Object.defineProperty(Phaser.AnimationManager.prototype, 'frame', {

    get: function () {

        if (this.currentFrame)
        {
            return this.currentFrame.index;
        }

    },

    set: function (value) {

        if (typeof value === 'number' && this._frameData && this._frameData.getFrame(value) !== null)
        {
            this.currentFrame = this._frameData.getFrame(value);

            if (this.currentFrame)
            {
                this.sprite.setFrame(this.currentFrame);

                if (this.sprite.__tilePattern)
                {
                    this.__tilePattern = false;
                    this.tilingTexture = false;
                }
            }
        }

    }

});

/**
 * @name Phaser.AnimationManager#frameName
 * @property {string} frameName - Gets or sets the current frame name and updates the Texture Cache for display.
 */
Object.defineProperty(Phaser.AnimationManager.prototype, 'frameName', {

    get: function () {

        if (this.currentFrame)
        {
            return this.currentFrame.name;
        }

    },

    set: function (value) {

        if (typeof value === 'string' && this._frameData.getFrameByName(value) !== null)
        {
            this.currentFrame = this._frameData.getFrameByName(value);

            if (this.currentFrame)
            {
                this._frameIndex = this.currentFrame.index;

                this.sprite.setFrame(this.currentFrame);

                if (this.sprite.__tilePattern)
                {
                    this.__tilePattern = false;
                    this.tilingTexture = false;
                }
            }
        }
        else
        {
            console.warn('Cannot set frameName: ' + value);
        }
    }

});

/**
 * Created by chenqx on 8/5/15.
 * @hack 添加三角形绘制提交方式
 */
/**
 * 以四边形填充
 * @constant
 * @type {number}
 */
qc.BATCH_QUAD = 0;
/**
 * 以三角形填充
 * @constant
 * @type {number}
 */
qc.BATCH_TRIANGLES  = 1;


var oldWebGLSpriteBatchSetContext = PIXI.WebGLSpriteBatch.prototype.setContext;
PIXI.WebGLSpriteBatch.prototype.setContext = function(gl) {
    oldWebGLSpriteBatchSetContext.call(this, gl);
    this.quadSize = this.size;
    this.triangleSize = 300;
    this.batchIndexNumber = 6;
    var triangleIndicesNum = this.triangleSize * 3;
    this.triangleIndices = new PIXI.Uint16Array(triangleIndicesNum);
    for (var i = 0; i < triangleIndicesNum; ++i) {
        this.triangleIndices[i] = i;
    }
    this._batchType = qc.BATCH_QUAD;
    this.quadIndexBuffer = this.indexBuffer;
    this.triangleIndexBuffer = gl.createBuffer();

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.triangleIndexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.triangleIndices, gl.STATIC_DRAW);
};

var oldWebGLSpriteBatchDestroy = PIXI.WebGLSpriteBatch.prototype.destroy;
PIXI.WebGLSpriteBatch.prototype.destroy = function() {
    this.triangleIndices = null;
    this.gl.deleteBuffer(this.triangleIndexBuffer);
    oldWebGLSpriteBatchDestroy.call(this);
}

Object.defineProperties(PIXI.WebGLSpriteBatch.prototype,{
    batchType : {
        get : function() { return this._batchType; },
        set : function(v) {
            if (v === this._batchType) {
                return;
            }
            this.stop();
            // 切换IndexBuffer，Size
            if (v === qc.BATCH_TRIANGLES) {
                this.size = this.triangleSize;
                this.indexBuffer = this.triangleIndexBuffer;
                this._batchType = v;
                this.batchIndexNumber = 3;
            }
            else {
                this.size = this.quadSize;
                this.indexBuffer = this.quadIndexBuffer;
                this._batchType = v;
                this.batchIndexNumber = 6;
            }
            this.start();
        }
    }
});

/**
 * @method renderBatch
 * @param texture {Texture}
 * @param size {Number}
 * @param startIndex {Number}
 */
PIXI.WebGLSpriteBatch.prototype.renderBatch = function(texture, size, startIndex)
{
    if(size === 0)return;

    var gl = this.gl;

    // check if a texture is dirty..
    if(texture._dirty[gl.id])
    {
        this.renderSession.renderer.updateTexture(texture);
    }
    else
    {
        // bind the current texture
        gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]);
    }
    //gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.batchIndexNumber === 3 ? this.triangleIndexBuffer : this.indexBuffer);
    // now draw those suckas!
    gl.drawElements(gl.TRIANGLES, size * this.batchIndexNumber, gl.UNSIGNED_SHORT, startIndex * this.batchIndexNumber * 2);

    // increment the draw count
    this.renderSession.drawCount++;
};
/**
 * @hackpp 在安卓uc浏览器下，在mask中绘制大小大概大于70*70时，会影响下一个超过128*128大小的图形绘制
 */
PIXI.CanvasMaskManager.prototype.popMask = function(renderSession)
{
    renderSession.context.restore();
    if (qc.__IS_ANDROID_UC) {
		var tempCanvas = qc._tempCanvas;
		if (!tempCanvas) {
			  tempCanvas = qc._tempCanvas = document.createElement('canvas');
			  tempCanvas.width = 128;
			  tempCanvas.height = 128;
		}
		renderSession.context.drawImage(tempCanvas, 0, 0, 2, 2, -5, -5, 1, 1);	
    }
	  
};
/**
 * @author linyw
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * Phaser2.3的实现未考虑sprite.tintedTexture已有的对象进行复用，
 * Phaser2.4考虑了老对象并对创建Canvas进行了池化：sprite.tintedTexture || PIXI.CanvasPool.create(this);
 * 我们先复用老的sprite.tintedTexture，后续可再考虑池化
 */
PIXI.CanvasTinter.getTintedTexture = function(sprite, color)
{
    var canvas = sprite.tintedTexture || document.createElement("canvas");

    PIXI.CanvasTinter.tintMethod(sprite.texture, color, canvas);

    return canvas;
};
/**
* @hack 替换pixi的DisplayObject，优化 updateTransform
*/

/*
* Updates the object transform for rendering
*
* @method updateTransform
* @private
*/
PIXI.DisplayObject.prototype.updateTransform = function() {
    var parent = this.parent;
    if (!parent) {
        return;
    }
    var needCalc = !this._isNotNeedCalcTransform || parent._isSubNeedCalcTransform;
    if (needCalc) {
        this.game && this.game._calcTransformCount++;
        var pt = this.parent.worldTransform;
        var wt = this.worldTransform;

        // temporary matrix variables
        var a, b, c, d, tx, ty;
        wt.inUpdate = true;
        // so if rotation is between 0 then we can simplify the multiplication process..
        if (this.rotation % PIXI.PI_2) {
            // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes
            if (this.rotation !== this.rotationCache) {
                this.rotationCache = this.rotation;
                this._sr = Math.sin(this.rotation);
                this._cr = Math.cos(this.rotation);
            }

            // get the matrix values of the displayobject based on its transform properties..
            a  =  this._cr * this.scale.x;
            b  =  this._sr * this.scale.x;
            c  = -this._sr * this.scale.y;
            d  =  this._cr * this.scale.y;
            tx =  this.position.x;
            ty =  this.position.y;

            // check for pivot.. not often used so geared towards that fact!
            if (this.pivot.x || this.pivot.y) {
                tx -= this.pivot.x * a + this.pivot.y * c;
                ty -= this.pivot.x * b + this.pivot.y * d;
            }

            // concat the parent matrix with the objects transform.
            wt.a  = a  * pt.a + b  * pt.c;
            wt.b  = a  * pt.b + b  * pt.d;
            wt.c  = c  * pt.a + d  * pt.c;
            wt.d  = c  * pt.b + d  * pt.d;
            wt.tx = tx * pt.a + ty * pt.c + pt.tx;
            wt.ty = tx * pt.b + ty * pt.d + pt.ty;
        }
        else {
            // lets do the fast version as we know there is no rotation..
            a  = this.scale.x;
            d  = this.scale.y;

            tx = this.position.x - this.pivot.x * a;
            ty = this.position.y - this.pivot.y * d;

            wt.a  = a  * pt.a;
            wt.b  = a  * pt.b;
            wt.c  = d  * pt.c;
            wt.d  = d  * pt.d;
            wt.tx = tx * pt.a + ty * pt.c + pt.tx;
            wt.ty = tx * pt.b + ty * pt.d + pt.ty;
        }
        this._isNotNeedCalcTransform = true;
        this._isSubNeedCalcTransform = true;

        if (this.worldTransformChangedCallback) {
            this.worldTransformChangedCallback.call(this.worldTransformChangedContext);
        }
    }
    else {
        this._isSubNeedCalcTransform = false;
        var wt = this.worldTransform;
        var pt = parent.worldTransform;
    }
    wt.inUpdate = false;
    // multiply the alphas..
    this.worldAlpha = this.alpha * this.parent.worldAlpha;

    //  Custom callback?
    if (this.transformCallback) {
        this.transformCallback.call(this.transformCallbackContext, wt, pt);
    }
};

/**
 * 替换updateTransform
 */
PIXI.DisplayObject.prototype.displayObjectUpdateTransform = PIXI.DisplayObject.prototype.updateTransform;

/**
 * 获取当前的worldTransform
 * @returns {PIXI.DisplayObject.worldTransform|*}
 */
PIXI.DisplayObject.prototype.getWorldTransform = function() {
    var isDirty = false;
    var parent = this;
    var lastDirtyNode = null;
    while (parent) {
        if (!parent._isNotNeedCalcTransform) {
            isDirty = true;
            lastDirtyNode = parent;
        }
        parent = parent.parent;
    }
    if (isDirty) {
        lastDirtyNode.updateTransform();
    }
    return this.worldTransform;
};
/**
 * @hack 替换pixi的_renderWebGL用来支持 softClip
 */
/**
 * Renders the object using the WebGL renderer
 *
 * @method _renderWebGL
 * @param renderSession {RenderSession}
 * @private
 */
PIXI.DisplayObjectContainer.prototype._renderWebGL = function(renderSession)
{
    if (!this.visible || this.alpha <= 0) return;

    if (this._cacheAsBitmap)
    {
        this._renderCachedSprite(renderSession);
        return;
    }

    var i;

    if (this.softClip) {
        SoftClipManager.getManager(renderSession).pushPolygon(this.softClip);
    }

    if (this._graphicsFilter || this._mask || this._filters)
    {
        if (this._graphicsFilter) {
            renderSession.spriteBatch.flush();
            renderSession.filterManager.pushFilter(this._graphicsFilter);
        }

        // push filter first as we need to ensure the stencil buffer is correct for any masking
        if (this._filters && !this.filterSelf)
        {
            renderSession.spriteBatch.flush();
            renderSession.filterManager.pushFilter(this._filterBlock);
        }

        if (this._mask)
        {
            renderSession.spriteBatch.stop();
            renderSession.maskManager.pushMask(this.mask, renderSession);
            renderSession.spriteBatch.start();
        }

        // simple render children!
        for (i = 0; i < this.children.length; i++)
        {
            this.children[i]._renderWebGL(renderSession);
        }

        renderSession.spriteBatch.stop();

        if (this._mask) renderSession.maskManager.popMask(this._mask, renderSession);
        if (this._filters && !this.filterSelf) renderSession.filterManager.popFilter();
        if (this._graphicsFilter) renderSession.filterManager.popFilter();
        renderSession.spriteBatch.start();
    }
    else
    {
        // simple render children!
        for (i = 0; i < this.children.length; i++)
        {
            this.children[i]._renderWebGL(renderSession);
        }
    }
    if (this.softClip) {
        SoftClipManager.getManager(renderSession).popPolygon();
    }
};

PIXI.DisplayObjectContainer.prototype.addChildAt = function(child, index)
{
    if(index >= 0 && index <= this.children.length)
    {
        if(child.parent)
        {
            child.parent.removeChild(child);
        }

        child.parent = this;

        this.children.splice(index, 0, child);
        this._qc && this._qc._dispatchChildrenChanged('add', [child._qc]);
        if(this.stage)child.setStageReference(this.stage);

        return child;
    }
    else
    {
        throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length);
    }
};

/**
 * Swaps the position of 2 Display Objects within this container.
 *
 * @method swapChildren
 * @param child {DisplayObject}
 * @param child2 {DisplayObject}
 */
PIXI.DisplayObjectContainer.prototype.swapChildren = function(child, child2)
{
    if(child === child2) {
        return;
    }

    var index1 = this.getChildIndex(child);
    var index2 = this.getChildIndex(child2);

    if(index1 < 0 || index2 < 0) {
        throw new Error('swapChildren: Both the supplied DisplayObjects must be a child of the caller.');
    }

    this.children[index1] = child2;
    this.children[index2] = child;
    this._qc && this._qc._dispatchChildrenChanged('order', [child._qc, child2._qc]);
};

/**
 * Changes the position of an existing child in the display object container
 *
 * @method setChildIndex
 * @param child {DisplayObject} The child DisplayObject instance for which you want to change the index number
 * @param index {Number} The resulting index number for the child display object
 */
PIXI.DisplayObjectContainer.prototype.setChildIndex = function(child, index)
{
    if (index < 0 || index >= this.children.length)
    {
        throw new Error('The supplied index is out of bounds');
    }
    var currentIndex = this.getChildIndex(child);
    this.children.splice(currentIndex, 1); //remove from old position
    this.children.splice(index, 0, child); //add at new position
    this._qc && this._qc._dispatchChildrenChanged('order', [child._qc]);
};

/**
 * Removes a child from the specified index position.
 *
 * @method removeChildAt
 * @param index {Number} The index to get the child from
 * @return {DisplayObject} The child that was removed.
 */
PIXI.DisplayObjectContainer.prototype.removeChildAt = function(index)
{
    var child = this.getChildAt( index );
    if(this.stage)
        child.removeStageReference();

    child.parent = undefined;
    this.children.splice( index, 1 );
    this._qc && this._qc._dispatchChildrenChanged('remove', [child._qc]);
    return child;
};

/**
 * Removes all children from this container that are within the begin and end indexes.
 *
 * @method removeChildren
 * @param beginIndex {Number} The beginning position. Default value is 0.
 * @param endIndex {Number} The ending position. Default value is size of the container.
 */
PIXI.DisplayObjectContainer.prototype.removeChildren = function(beginIndex, endIndex)
{
    var begin = beginIndex || 0;
    var end = typeof endIndex === 'number' ? endIndex : this.children.length;
    var range = end - begin;

    if (range > 0 && range <= end)
    {
        var removed = this.children.splice(begin, range);
        var removedQCNode = [];
        for (var i = 0; i < removed.length; i++) {
            var child = removed[i];
            if(this.stage)
                child.removeStageReference();
            child.parent = undefined;
            if (child._qc) {
                removedQCNode.push(child._qc);
            }
        }
        this._qc && this._qc._dispatchChildrenChanged('remove', removedQCNode);
        return removed;
    }
    else if (range === 0 && this.children.length === 0)
    {
        this._qc && this._qc._dispatchChildrenChanged('remove', []);
        return [];
    }
    else
    {
        throw new Error( 'removeChildren: Range Error, numeric values are outside the acceptable range' );
    }
};

/**
 * 获取本地节点范围
 * @returns {Rectangle}
 */
PIXI.DisplayObjectContainer.prototype.getLocalBounds = function()
{
    var matrixCache = this.worldTransform;
    this._isSubNeedCalcTransform = true;
    this.worldTransform = PIXI.identityMatrix;

    for(var i=0,j=this.children.length; i<j; i++)
    {
        this.children[i].updateTransform();
    }

    var bounds = this.getBounds();

    this.worldTransform = matrixCache;
    this._isSubNeedCalcTransform = true;
    this._isNotNeedCalcTransform = false;
    return bounds;
};
/**
 * @hack 添加原图参数
 */
/**
 * Initialises the shader.
 *
 * @method init
 */
PIXI.PixiShader.prototype.init = function()
{
    var gl = this.gl;

    var program = PIXI.compileProgram(gl, this.vertexSrc || PIXI.PixiShader.defaultVertexSrc, this.fragmentSrc);

    gl.useProgram(program);

    // get and store the uniforms for the shader
    this.uSampler = gl.getUniformLocation(program, 'uSampler');
    // modify by chenqx, 用来传递 Shader 处理之前的原图
    this.uSourceSampler = gl.getUniformLocation(program, 'uSourceSampler');
    // modify end
    this.projectionVector = gl.getUniformLocation(program, 'projectionVector');
    this.offsetVector = gl.getUniformLocation(program, 'offsetVector');
    this.dimensions = gl.getUniformLocation(program, 'dimensions');
    this.pixelSize = gl.getUniformLocation(program, 'pixelSize');

    // get and store the attributes
    this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition');
    this.aTextureCoord = gl.getAttribLocation(program, 'aTextureCoord');
    this.colorAttribute = gl.getAttribLocation(program, 'aColor');

    // Begin worst hack eva //

    // WHY??? ONLY on my chrome pixel the line above returns -1 when using filters?
    // maybe its something to do with the current state of the gl context.
    // I'm convinced this is a bug in the chrome browser as there is NO reason why this should be returning -1 especially as it only manifests on my chrome pixel
    // If theres any webGL people that know why could happen please help :)
    if(this.colorAttribute === -1)
    {
        this.colorAttribute = 2;
    }

    this.attributes = [this.aVertexPosition, this.aTextureCoord, this.colorAttribute];

    if (this.extraAttribute) {
        for (var idx in this.extraAttribute) {
            var key = this.extraAttribute[idx].name;
            this[key] = gl.getAttribLocation(program, key);
            this.attributes.push(this[key]);
        }
    }
    // End worst hack eva //

    // add those custom shaders!
    for (var key in this.uniforms)
    {
        // get the uniform locations..
        this.uniforms[key].uniformLocation = gl.getUniformLocation(program, key);
    }

    this.initUniforms();

    this.program = program;
};

/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * Renders the object using the WebGL renderer
 *
 * @method _renderWebGL
 * @param renderSession {RenderSession}
 * @private
 */
PIXI.Sprite.prototype._renderWebGL = function(renderSession)
{
    // if the sprite is not visible or the alpha is 0 then no need to render this element
    if (!this.visible || this.alpha <= 0 || !this.renderable) return;

    var i, j;
    if (this.softClip) {
        SoftClipManager.getManager(renderSession).pushPolygon(this.softClip);
    }
    // do a quick check to see if this element has a mask or a filter.
    if (this.maskPixel) {
        var spriteBatch =  renderSession.spriteBatch;
        spriteBatch.flush();
        renderSession.filterManager.pushFilter(this.maskPixel);
        for (i = 0; i < this.children.length; i++)
        {
            this.children[i]._renderWebGL(renderSession);
        }
        spriteBatch.stop();
        renderSession.filterManager.popFilter();
        spriteBatch.start();
    }
    else if (this._graphicsFilter || this._mask || this._filters) {
        var spriteBatch =  renderSession.spriteBatch;

        if (this._graphicsFilter) {
            spriteBatch.flush();
            renderSession.filterManager.pushFilter(this._graphicsFilter);
        }

        // push filter first as we need to ensure the stencil buffer is correct for any masking
        if (this._filters && !this.filterSelf)
        {
            spriteBatch.flush();
            renderSession.filterManager.pushFilter(this._filterBlock);
        }

        if (this._mask)
        {
            spriteBatch.stop();
            renderSession.maskManager.pushMask(this.mask, renderSession);
            spriteBatch.start();
        }
        if (this._filters && this.filterSelf)
        {
            spriteBatch.stop();
            //renderSession.filterManager.pushFilter(this._filterBlock);
            var oldTexture = this.texture.baseTexture._glTextures[renderSession.gl.id];
            var texture = renderSession.filterManager.buildFilterTexture(renderSession, this._filterBlock, this);
            spriteBatch.start();
            var olduvs = this.texture._uvs;
            if (texture) {
                this.texture.baseTexture._glTextures[renderSession.gl.id] = texture.texture;
            }
            this._renderWebGLImpl(renderSession);
            spriteBatch.flush();
            if (texture) {
                this.texture.baseTexture._glTextures[renderSession.gl.id] = oldTexture;
                renderSession.filterManager.texturePool.push(texture);
                this.texture._uvs = olduvs;
            }
        }
        else {
            this._renderWebGLImpl(renderSession);
        }
        // add this sprite to the batch

        // now loop through the children and make sure they get rendered
        for (i = 0; i < this.children.length; i++)
        {
            this.children[i]._renderWebGL(renderSession);
        }

        // time to stop the sprite batch as either a mask element or a filter draw will happen next
        spriteBatch.stop();
        if (this._mask) renderSession.maskManager.popMask(this._mask, renderSession);
        if (this._filters && !this.filterSelf) renderSession.filterManager.popFilter();
        if (this._graphicsFilter) renderSession.filterManager.popFilter();
        spriteBatch.start();
    }
    else
    {
        this._renderWebGLImpl(renderSession);
        // simple render children!
        for (i = 0; i < this.children.length; i++)
        {
            this.children[i]._renderWebGL(renderSession);
        }

    }

    if (this.softClip) {
        SoftClipManager.getManager(renderSession).popPolygon();
    }
};


PIXI.WebGLStencilManager.prototype.pushSpriteStencil = function(sprite, renderSession)
{
    var gl = this.gl;
    if(this.stencilStack.length === 0)
    {
        gl.enable(gl.STENCIL_TEST);
        gl.clear(gl.STENCIL_BUFFER_BIT);
        this.reverse = true;
        this.count = 0;
    }

    this.stencilStack.push(sprite);

    var level = this.count;

    gl.colorMask(false, false, false, false);

    gl.stencilFunc(gl.ALWAYS,0,0xFF);
    gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT);

    if(!this.reverse)
    {
        gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF);
        gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR);
    }
    else
    {
        gl.stencilFunc(gl.EQUAL,level, 0xFF);
        gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR);
    }
    sprite._renderWebGLImpl(renderSession);
    renderSession.spriteBatch.flush();
    //gl.drawElements(gl.TRIANGLE_STRIP,  webGLData.indices.length, gl.UNSIGNED_SHORT, 0 );

    if(!this.reverse)
    {
        gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF);
    }
    else
    {
        gl.stencilFunc(gl.EQUAL,level+1, 0xFF);
    }

    gl.colorMask(true, true, true, true);
    gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP);

    this.count++;
};

/**
 * @method popStencil
 * @param graphics {Graphics}
 * @param webGLData {Array}
 * @param renderSession {Object}
 */
PIXI.WebGLStencilManager.prototype.popSpriteStencil = function(sprite, renderSession)
{
    var gl = this.gl;
    this.stencilStack.pop();

    this.count--;

    if(this.stencilStack.length === 0)
    {
        // the stack is empty!
        gl.disable(gl.STENCIL_TEST);

    }
    else
    {

        var level = this.count;
        gl.colorMask(false, false, false, false);

        //  console.log("<<>>")
        if(!this.reverse)
        {
            gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF);
            gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR);
        }
        else
        {
            gl.stencilFunc(gl.EQUAL,level+1, 0xFF);
            gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR);
        }

        sprite._renderWebGLImpl(renderSession);
        renderSession.spriteBatch.flush();
        if(!this.reverse)
        {
            gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF);
        }
        else
        {
            gl.stencilFunc(gl.EQUAL,level, 0xFF);
        }

        gl.colorMask(true, true, true, true);
        gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP);
    }
};

/**
 * 根据裁切方式选择绘制方式
 * @param renderSession
 * @private
 */
PIXI.Sprite.prototype._renderWebGLImpl = function(renderSession) {
    var clipMgr = renderSession.softClipManager;
    var spriteBatch =  renderSession.spriteBatch;
    if (clipMgr && clipMgr.needClip) {
        var texture = this.texture;
        var resolution = texture.baseTexture.resolution;
        var worldTransform = this.worldTransform;
        var uvs = texture._uvs;
        if (! uvs) return;
        var a = worldTransform.a / resolution;
        var b = worldTransform.b / resolution;
        var c = worldTransform.c / resolution;
        var d = worldTransform.d / resolution;
        var tx = worldTransform.tx;
        var ty = worldTransform.ty;
        var w0, w1, h0, h1;
        var aX = this.anchor.x;
        var aY = this.anchor.y;
        if (texture.trim)
        {
            // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords..
            var trim = texture.trim;

            w1 = trim.x - aX * trim.width;
            w0 = w1 + texture.crop.width;

            h1 = trim.y - aY * trim.height;
            h0 = h1 + texture.crop.height;

        }
        else
        {
            w0 = (texture.frame.width ) * (1-aX);
            w1 = (texture.frame.width ) * -aX;

            h0 = texture.frame.height * (1-aY);
            h1 = texture.frame.height * -aY;
        }
        var tint = this.tint;
        tint = (tint >> 16) + (tint & 0xff00) + ((tint & 0xff) << 16) +
        (this.worldAlpha * 255 << 24);

        clipMgr.renderSprite(renderSession, this,
            w1, h1, w0, h0,
            uvs.x0, uvs.y0, uvs.x2, uvs.y2,
            a, b, c, d, tx, ty,
            tint);
    }
    else {
        spriteBatch.render(this);
    }
};

/**
 * Renders the object using the Canvas renderer
 *
 * @method _renderCanvas
 * @param renderSession {RenderSession}
 * @private
 */
PIXI.Sprite.prototype._renderCanvas = function(renderSession)
{
    // If the sprite is not visible or the alpha is 0 then no need to render this element
    if (this.visible === false || this.alpha === 0 || this.renderable === false || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) return;

    if (this.maskPixel) {
        var bufferPool = renderSession.bufferPool || ( renderSession.bufferPool = []);
        var oldContext = renderSession.context;
        var oldOffX = (oldContext.globalOffX || 0);
        var oldOffY = (oldContext.globalOffY || 0);

        var filterArea = this.maskPixel.target.filterArea || this.maskPixel.target.getBounds();
        var minX = Math.max(oldOffX, filterArea.x);
        var minY = Math.max(oldOffY, filterArea.y);
        var maxX = Math.min(filterArea.x + filterArea.width, oldContext.canvas.width + oldOffX);
        var maxY = Math.min(filterArea.y + filterArea.height, oldContext.canvas.height + oldOffY);
        filterArea.x = minX;
        filterArea.y = minY;
        filterArea.width = maxX - minX;
        filterArea.height = maxY - minY;

        var canvasBuffer =  bufferPool.pop();
        if (!canvasBuffer) {
            canvasBuffer = new PIXI.CanvasBuffer(renderSession.context.canvas.width * renderSession.resolution, renderSession.context.canvas.height * renderSession.resolution);
            canvasBuffer.context._setTransform = canvasBuffer.context.setTransform;
            canvasBuffer.context.setTransform = function(a, b, c, d, tx, ty) {
                this._setTransform(a, b, c, d, tx - (this.globalOffX || 0), ty - (this.globalOffY || 0));
            };
        }
        canvasBuffer.resize(filterArea.width * renderSession.resolution, filterArea.height * renderSession.resolution);
        canvasBuffer.context.clearRect(0, 0, filterArea.width * renderSession.resolution, filterArea.height * renderSession.resolution);
        canvasBuffer.context.globalOffX = filterArea.x * renderSession.resolution + oldOffX;
        canvasBuffer.context.globalOffY = filterArea.y * renderSession.resolution + oldOffY;
        renderSession.context = canvasBuffer.context;
    }

    if (this.blendMode !== renderSession.currentBlendMode)
    {
        renderSession.currentBlendMode = this.blendMode;
        renderSession.context.globalCompositeOperation = PIXI.blendModesCanvas[renderSession.currentBlendMode];
    }

    if (this._mask)
    {
        renderSession.maskManager.pushMask(this._mask, renderSession);
    }

    //  Ignore null sources
    if (this.texture.valid)
    {
        var resolution = this.texture.baseTexture.resolution / renderSession.resolution;

        renderSession.context.globalAlpha = this.worldAlpha;

        //  If smoothingEnabled is supported and we need to change the smoothing property for this texture
        if (renderSession.smoothProperty && renderSession.scaleMode !== this.texture.baseTexture.scaleMode)
        {
            renderSession.scaleMode = this.texture.baseTexture.scaleMode;
            renderSession.context[renderSession.smoothProperty] = (renderSession.scaleMode === PIXI.scaleModes.LINEAR);
        }

        //  If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions
        var dx = (this.texture.trim) ? this.texture.trim.x - this.anchor.x * this.texture.trim.width : this.anchor.x * -this.texture.frame.width;
        var dy = (this.texture.trim) ? this.texture.trim.y - this.anchor.y * this.texture.trim.height : this.anchor.y * -this.texture.frame.height;

        //  Allow for pixel rounding
        if (renderSession.roundPixels)
        {
            renderSession.context.setTransform(
                this.worldTransform.a,
                this.worldTransform.b,
                this.worldTransform.c,
                this.worldTransform.d,
                (this.worldTransform.tx * renderSession.resolution) | 0,
                (this.worldTransform.ty * renderSession.resolution) | 0);
            dx = dx | 0;
            dy = dy | 0;
        }
        else
        {
            renderSession.context.setTransform(
                this.worldTransform.a,
                this.worldTransform.b,
                this.worldTransform.c,
                this.worldTransform.d,
                this.worldTransform.tx * renderSession.resolution,
                this.worldTransform.ty * renderSession.resolution);
        }
        if (!this.maskPixel) {
            if (this.tint !== 0xFFFFFF) {
                if (this.cachedTint !== this.tint) {
                    this.cachedTint = this.tint;
                    this.tintedTexture = PIXI.CanvasTinter.getTintedTexture(this, this.tint);
                }

                renderSession.context.drawImage(
                    this.tintedTexture,
                    0,
                    0,
                    this.texture.crop.width,
                    this.texture.crop.height,
                    dx / resolution,
                    dy / resolution,
                    this.texture.frame.width / resolution,
                    this.texture.frame.height / resolution);
            }
            else {
                renderSession.context.drawImage(
                    this.texture.baseTexture.source,
                    this.texture.crop.x,
                    this.texture.crop.y,
                    this.texture.crop.width,
                    this.texture.crop.height,
                    dx / resolution,
                    dy / resolution,
                    this.texture.frame.width / resolution,
                    this.texture.frame.height / resolution);
            }
        }
    }

    // OVERWRITE
    for (var i = 0; i < this.children.length; i++)
    {
        this.children[i]._renderCanvas(renderSession);
    }

    if (this._mask)
    {
        renderSession.maskManager.popMask(renderSession);
    }

    if (this.maskPixel && this.texture.valid) {
        var context = renderSession.context;
        context.globalCompositeOperation = 'destination-in';
        //  Allow for pixel rounding
        if (renderSession.roundPixels)
        {
            context.setTransform(
                this.worldTransform.a,
                this.worldTransform.b,
                this.worldTransform.c,
                this.worldTransform.d,
                (this.worldTransform.tx * renderSession.resolution) | 0,
                (this.worldTransform.ty * renderSession.resolution) | 0);
            dx = dx | 0;
            dy = dy | 0;
        }
        else
        {
            context.setTransform(
                this.worldTransform.a,
                this.worldTransform.b,
                this.worldTransform.c,
                this.worldTransform.d,
                this.worldTransform.tx * renderSession.resolution,
                this.worldTransform.ty * renderSession.resolution);
        }
        context.drawImage(
            this.texture.baseTexture.source,
            this.texture.crop.x,
            this.texture.crop.y,
            this.texture.crop.width,
            this.texture.crop.height,
            dx / resolution,
            dy / resolution,
            this.texture.crop.width / resolution,
            this.texture.crop.height / resolution);

        context.globalCompositeOperation = 'source-over';

        renderSession.context = oldContext;
        if (renderSession.roundPixels)
        {
            renderSession.context.setTransform(
                1, 0, 0, 1,
                filterArea.x  * renderSession.resolution | 0,
                filterArea.y  * renderSession.resolution| 0);
        }
        else
        {
            renderSession.context.setTransform(
                1, 0, 0, 1,
                filterArea.x * renderSession.resolution,
                filterArea.y * renderSession.resolution);
        }
        renderSession.context.drawImage(canvasBuffer.canvas,
            0, 0, canvasBuffer.canvas.width, canvasBuffer.canvas.height,
            0, 0, canvasBuffer.canvas.width, canvasBuffer.canvas.height);
        bufferPool.push(canvasBuffer);
    }
};

/**
 * Created by chenqx on 8/5/15.
 * @hack 添加三角形绘制提交方式
 */
var oldWebGLFastSpriteBatchSetContext = PIXI.WebGLFastSpriteBatch.prototype.setContext;
PIXI.WebGLFastSpriteBatch.prototype.setContext = function(gl) {
    oldWebGLFastSpriteBatchSetContext.call(this, gl);
    this.quadSize = this.size;
    this.triangleSize = 300;
    this.batchIndexNumber = 6;
    var triangleIndicesNum = this.triangleSize * 3;
    this.triangleIndices = new PIXI.Uint16Array(triangleIndicesNum);
    for (var i = 0; i < triangleIndicesNum; ++i) {
        this.triangleIndices[i] = i;
    }
    this._batchType = qc.BATCH_QUAD;
    this.quadIndexBuffer = this.indexBuffer;
    this.triangleIndexBuffer = gl.createBuffer();

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.triangleIndexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.triangleIndices, gl.STATIC_DRAW);
};

var oldWebGLFastSpriteBatchDestroy = PIXI.WebGLFastSpriteBatch.prototype.destroy;
PIXI.WebGLFastSpriteBatch.prototype.destroy = function() {
    this.triangleIndices = null;
    this.gl.deleteBuffer(this.triangleIndexBuffer);
    oldWebGLFastSpriteBatchDestroy.call(this);
}

Object.defineProperties(PIXI.WebGLFastSpriteBatch.prototype,{
    batchType : {
        get : function() { return this._batchType; },
        set : function(v) {
            if (v === this._batchType) {
                return;
            }
            this.stop();
            // 切换IndexBuffer，Size
            if (v === qc.BATCH_TRIANGLES) {
                this.size = this.triangleSize;
                this.indexBuffer = this.triangleIndexBuffer;
                this._batchType = v;
                this.batchIndexNumber = 3;
            }
            else {
                this.size = this.quadSize;
                this.indexBuffer = this.quadIndexBuffer;
                this._batchType = v;
                this.batchIndexNumber = 6;
            }
            this.start();
        }
    }
});

PIXI.WebGLFastSpriteBatch.prototype.flush = function()
{
    // If the batch is length 0 then return as there is nothing to draw
    if (this.currentBatchSize===0)return;

    var gl = this.gl;

    // bind the current texture

    if(!this.currentBaseTexture._glTextures[gl.id])this.renderSession.renderer.updateTexture(this.currentBaseTexture, gl);

    gl.bindTexture(gl.TEXTURE_2D, this.currentBaseTexture._glTextures[gl.id]);

    // upload the verts to the buffer

    if(this.currentBatchSize > ( this.size * 0.5 ) )
    {
        gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices);
    }
    else
    {
        var view = this.vertices.subarray(0, this.currentBatchSize * 4 * this.vertSize);

        gl.bufferSubData(gl.ARRAY_BUFFER, 0, view);
    }
    //gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.batchIndexNumber === 3 ? this.triangleIndexBuffer : this.indexBuffer);
    // now draw those suckas!
    gl.drawElements(gl.TRIANGLES, this.currentBatchSize * this.batchIndexNumber, gl.UNSIGNED_SHORT, 0);

    // then reset the batch!
    this.currentBatchSize = 0;

    // increment the draw count
    this.renderSession.drawCount++;
};

// @hackpp PIXI.WebGLFilterManager 中 mask 和 filter 不兼容的问题

/**
 * Initialises the shader buffers.
 *
 * @method initShaderBuffers
 */
PIXI.WebGLFilterManager.prototype.initShaderBuffers = function()
{
    var gl = this.gl;

    // create some buffers
    this.vertexBuffer = gl.createBuffer();
    this.uvBuffer = gl.createBuffer();
    this.colorBuffer = gl.createBuffer();
    this.indexBuffer = gl.createBuffer();

    // bind and upload the vertexs..
    // keep a reference to the vertexFloatData..
    this.vertexArray = new PIXI.Float32Array([0.0, 0.0,
        1.0, 0.0,
        0.0, 1.0,
        1.0, 1.0]);

    gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW);

    // bind and upload the uv buffer
    this.uvArray = new PIXI.Float32Array(Filter.MAX_SAMPLE_COUNT * 8);
    //  [0.0, 0.0,
    //    1.0, 0.0,
    //    0.0, 1.0,
    //    1.0, 1.0]

    gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW);

    this.colorArray = new PIXI.Float32Array([1.0, 0xFFFFFF,
        1.0, 0xFFFFFF,
        1.0, 0xFFFFFF,
        1.0, 0xFFFFFF]);

    gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW);

    // bind and upload the index
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW);

};

/**
 * Applies the filter and adds it to the current filter stack.
 *
 * @method pushFilter
 * @param filterBlock {Object} the filter that will be pushed to the current filter stack
 */
PIXI.WebGLFilterManager.prototype.pushFilter = function(filterBlock)
{
    var gl = this.gl;

    var projection = this.renderSession.projection;
    var offset = this.renderSession.offset;

    filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds();

    // modify by chenqx
    filterBlock._previous_stencil_mgr = this.renderSession.stencilManager;
    this.renderSession.stencilManager = new PIXI.WebGLStencilManager();
    this.renderSession.stencilManager.setContext(gl);
    gl.disable(gl.STENCIL_TEST);
    // modify end

    // filter program
    // OPTIMISATION - the first filter is free if its a simple color change?
    this.filterStack.push(filterBlock);

    var filter = filterBlock.filterPasses[0];

    this.offsetX += filterBlock._filterArea.x;
    this.offsetY += filterBlock._filterArea.y;

    var texture = this.texturePool.pop();
    if(!texture)
    {
        texture = new PIXI.FilterTexture(this.gl, this.width, this.height);
    }
    else
    {
        texture.resize(this.width, this.height);
    }

    gl.bindTexture(gl.TEXTURE_2D,  texture.texture);

    var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea;

    var padding = filter.padding;
    filterArea.x -= padding;
    filterArea.y -= padding;
    filterArea.width += padding * 2;
    filterArea.height += padding * 2;

    // cap filter to screen size..
    /**
    if(filterArea.x < 0)filterArea.x = 0;
    if(filterArea.width > this.width)filterArea.width = this.width;
    if(filterArea.y < 0)filterArea.y = 0;
    if(filterArea.height > this.height)filterArea.height = this.height;
    **/
    //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA,  filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
    gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer);

    // set view port
    gl.viewport(0, 0, filterArea.width, filterArea.height);

    projection.x = filterArea.width/2;
    projection.y = -filterArea.height/2;

    offset.x = -filterArea.x;
    offset.y = -filterArea.y;


    // update projection
    // now restore the regular shader..
    // this.renderSession.shaderManager.setShader(this.defaultShader);
    //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2);
    //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y);

    gl.colorMask(true, true, true, true);
    gl.clearColor(0, 0, 0, 0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    filterBlock._glFilterTexture = texture;

};

/**
 * Removes the last filter from the filter stack and doesn't return it.
 *
 * @method popFilter
 */
PIXI.WebGLFilterManager.prototype.popFilter = function()
{
    var gl = this.gl;
    var filterBlock = this.filterStack.pop();
    var filterArea = filterBlock._filterArea;
    var texture = filterBlock._glFilterTexture;
    var tempTexture = filterBlock._glFilterTexture;
    var projection = this.renderSession.projection;
    var offset = this.renderSession.offset;

    if(filterBlock.filterPasses.length > 1)
    {
        gl.viewport(0, 0, filterArea.width, filterArea.height);

        gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);

        this.vertexArray[0] = 0;
        this.vertexArray[1] = filterArea.height;

        this.vertexArray[2] = filterArea.width;
        this.vertexArray[3] = filterArea.height;

        this.vertexArray[4] = 0;
        this.vertexArray[5] = 0;

        this.vertexArray[6] = filterArea.width;
        this.vertexArray[7] = 0;

        gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray);

        gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
        // now set the uvs..
        this.uvArray[0] = 0;
        this.uvArray[1] = 0;
        this.uvArray[2] = filterArea.width/this.width;
        this.uvArray[3] = 0;
        this.uvArray[4] = 0;
        this.uvArray[5] = filterArea.height/this.height;
        this.uvArray[6] = filterArea.width/this.width;
        this.uvArray[7] = filterArea.height/this.height;

        gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray);

        var inputTexture = texture;
        var outputTexture = this.texturePool.pop();
        if(!outputTexture)outputTexture = new PIXI.FilterTexture(this.gl, this.width, this.height);
        outputTexture.resize(this.width, this.height);

        // need to clear this FBO as it may have some left over elements from a previous filter.
        gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer );
        gl.clear(gl.COLOR_BUFFER_BIT);

        gl.disable(gl.BLEND);

        for (var i = 0; i < filterBlock.filterPasses.length-1; i++)
        {
            var filterPass = filterBlock.filterPasses[i];
            var extraAttribute = filterPass.extraAttribute;
            if (extraAttribute) {
                gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
                for (var idx = 0; idx < extraAttribute.length; ++idx) {
                    var filterTexture = filterPass.uniforms[extraAttribute[idx].texture].value;
                    if (!filterTexture)
                        continue;
                    var spriteUVS = filterTexture._uvs;
                    this.uvArray[8 * (idx + 1)] = spriteUVS.x3;
                    this.uvArray[8 * (idx + 1) + 1] = spriteUVS.y3;
                    this.uvArray[8 * (idx + 1) + 2] = spriteUVS.x2;
                    this.uvArray[8 * (idx + 1) + 3] = spriteUVS.y2;
                    this.uvArray[8 * (idx + 1) + 4] = spriteUVS.x0;
                    this.uvArray[8 * (idx + 1) + 5] = spriteUVS.y0;
                    this.uvArray[8 * (idx + 1) + 6] = spriteUVS.x1;
                    this.uvArray[8 * (idx + 1) + 7] = spriteUVS.y1;
                }
                gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray);
            }
            gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer );

            // set texture
            gl.activeTexture(gl.TEXTURE0);
            gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture);

            // draw texture..
            //filterPass.applyFilterPass(filterArea.width, filterArea.height);
            this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height, tempTexture);

            // swap the textures..
            var temp = inputTexture;
            inputTexture = outputTexture;
            if (temp === tempTexture) {
                outputTexture = this.texturePool.pop();
                if(!outputTexture)outputTexture = new PIXI.FilterTexture(this.gl, this.width, this.height);
            }
            else {
                outputTexture = temp;
            }
        }

        gl.enable(gl.BLEND);

        texture = inputTexture;
        this.texturePool.push(outputTexture);
    }

    var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1];

    this.offsetX -= filterArea.x;
    this.offsetY -= filterArea.y;

    var sizeX = this.width;
    var sizeY = this.height;

    var offsetX = 0;
    var offsetY = 0;

    var buffer = this.buffer;

    // time to render the filters texture to the previous scene
    if(this.filterStack.length === 0)
    {
        gl.colorMask(true, true, true, true);//this.transparent);
    }
    else
    {
        var currentFilter = this.filterStack[this.filterStack.length-1];
        filterArea = currentFilter._filterArea;

        sizeX = filterArea.width;
        sizeY = filterArea.height;

        offsetX = filterArea.x;
        offsetY = filterArea.y;

        buffer =  currentFilter._glFilterTexture.frameBuffer;
    }

    // TODO need to remove these global elements..
    projection.x = sizeX/2;
    projection.y = -sizeY/2;

    offset.x = offsetX;
    offset.y = offsetY;

    filterArea = filterBlock._filterArea;

    var x = filterArea.x-offsetX;
    var y = filterArea.y-offsetY;

    // update the buffers..
    // make sure to flip the y!
    gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);

    this.vertexArray[0] = x;
    this.vertexArray[1] = y + filterArea.height;

    this.vertexArray[2] = x + filterArea.width;
    this.vertexArray[3] = y + filterArea.height;

    this.vertexArray[4] = x;
    this.vertexArray[5] = y;

    this.vertexArray[6] = x + filterArea.width;
    this.vertexArray[7] = y;

    gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray);

    gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
    this.uvArray[0] = 0;
    this.uvArray[1] = 0;
    this.uvArray[2] = filterArea.width/this.width;
    this.uvArray[3] = 0;
    this.uvArray[4] = 0;
    this.uvArray[5] = filterArea.height/this.height;
    this.uvArray[6] = filterArea.width/this.width;
    this.uvArray[7] = filterArea.height/this.height;

    var extraAttribute = filter.extraAttribute;
    if (extraAttribute) {
        for (var idx = 0; idx < extraAttribute.length; ++idx) {
            var filterTexture = filter.uniforms[extraAttribute[idx].texture].value;
            if (!filterTexture)
                continue;
            var spriteUVS = filterTexture._uvs;
            this.uvArray[8 * (idx + 1)] = spriteUVS.x3;
            this.uvArray[8 * (idx + 1) + 1] = spriteUVS.y3;
            this.uvArray[8 * (idx + 1) + 2] = spriteUVS.x2;
            this.uvArray[8 * (idx + 1) + 3] = spriteUVS.y2;
            this.uvArray[8 * (idx + 1) + 4] = spriteUVS.x0;
            this.uvArray[8 * (idx + 1) + 5] = spriteUVS.y0;
            this.uvArray[8 * (idx + 1) + 6] = spriteUVS.x1;
            this.uvArray[8 * (idx + 1) + 7] = spriteUVS.y1;
        }
    }

    gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray);

    gl.viewport(0, 0, sizeX * this.renderSession.resolution, sizeY * this.renderSession.resolution);

    // bind the buffer
    gl.bindFramebuffer(gl.FRAMEBUFFER, buffer );

    // set the blend mode!
    //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)

    // set texture
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, texture.texture);

    // modify by chenqx
    if (this.renderSession.stencilManager) {
        this.renderSession.stencilManager.destroy();
    }
    this.renderSession.stencilManager = filterBlock._previous_stencil_mgr;
    filterBlock._previous_stencil_mgr = null;
    if (this.renderSession.stencilManager.count > 0) {
        gl.enable(gl.STENCIL_TEST);
    }
    else {
        gl.disable(gl.STENCIL_TEST);
    }
    // modify end

    // apply!
    this.applyFilterPass(filter, filterArea, sizeX, sizeY, tempTexture);

    // now restore the regular shader.. should happen automatically now..
    // this.renderSession.shaderManager.setShader(this.defaultShader);
    // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2);
    // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY);
    if (texture !== tempTexture) {
        this.texturePool.push(tempTexture);
    }
    // return the texture to the pool
    this.texturePool.push(texture);
    filterBlock._glFilterTexture = null;
};

PIXI.WebGLFilterManager.prototype.buildFilterTexture = function(renderSession, filterBlock, sprite) {
    var gl = renderSession.gl;
    var enabledStencilTest = gl.isEnabled(gl.STENCIL_TEST);
    if (enabledStencilTest) {
        gl.disable(gl.STENCIL_TEST);
    }
    var baseTexture = sprite.texture.baseTexture;
    if(!baseTexture._glTextures[gl.id]) {
        renderSession.renderer.updateTexture(baseTexture, gl);
    }
    var glTexture = baseTexture._glTextures[gl.id];
    var tempTexture = { texture : glTexture };
    var textureSize = new qc.Rectangle(0, 0, baseTexture.width, baseTexture.height);
    var spriteSize = sprite.texture.crop;
    var spriteUVS = sprite.texture._uvs;

    var outputTexture = this.texturePool.pop();
    if(!outputTexture) {
        outputTexture = new PIXI.FilterTexture(this.gl, textureSize.width, textureSize.height);
    }
    outputTexture.resize(textureSize.width, textureSize.height);

    // need to clear this FBO as it may have some left over elements from a previous filter.
    gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer);
    gl.viewport(0, 0, textureSize.width, textureSize.height);
    gl.colorMask(true, true, true, true);
    gl.clearColor(0, 0, 0, 0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.disable(gl.BLEND);

    gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);

    this.vertexArray[0] = spriteSize.x;
    this.vertexArray[1] = textureSize.height - spriteSize.y;

    this.vertexArray[2] = spriteSize.x + spriteSize.width;
    this.vertexArray[3] = textureSize.height - spriteSize.y;

    this.vertexArray[4] = spriteSize.x;
    this.vertexArray[5] = textureSize.height - spriteSize.y - spriteSize.height;

    this.vertexArray[6] = spriteSize.x + spriteSize.width;
    this.vertexArray[7] = textureSize.height - spriteSize.y - spriteSize.height;

    gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray);

    var filterPass = filterBlock.filterPasses[0];
    gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
    // now set the uvs..
    this.uvArray[0] = spriteUVS.x0;
    this.uvArray[1] = spriteUVS.y0;
    this.uvArray[2] = spriteUVS.x1;
    this.uvArray[3] = spriteUVS.y1;
    this.uvArray[4] = spriteUVS.x3;
    this.uvArray[5] = spriteUVS.y3;
    this.uvArray[6] = spriteUVS.x2;
    this.uvArray[7] = spriteUVS.y2;
    var extraAttribute = filterPass.extraAttribute;
    if (extraAttribute) {
        for (var idx = 0; idx < extraAttribute.length; ++idx) {
            var filterTexture = filterPass.uniforms[extraAttribute[idx].texture].value;
            if (!filterTexture)
                continue;
            var spriteUVS = filterTexture._uvs;
            this.uvArray[8 * (idx + 1)] = spriteUVS.x0;
            this.uvArray[8 * (idx + 1) + 1] = spriteUVS.y0;
            this.uvArray[8 * (idx + 1) + 2] = spriteUVS.x1;
            this.uvArray[8 * (idx + 1) + 3] = spriteUVS.y1;
            this.uvArray[8 * (idx + 1) + 4] = spriteUVS.x3;
            this.uvArray[8 * (idx + 1) + 5] = spriteUVS.y3;
            this.uvArray[8 * (idx + 1) + 6] = spriteUVS.x2;
            this.uvArray[8 * (idx + 1) + 7] = spriteUVS.y2;
        }
    }
    gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray);

    // set texture
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, tempTexture.texture);

    this.applyFilterPass(filterPass, new qc.Rectangle(0, 0, textureSize.width, textureSize.height), textureSize.width, textureSize.height, tempTexture, textureSize.width, textureSize.height);

    var inputTexture = null;

    for (var i = 1; i < filterBlock.filterPasses.length; i++)
    {
        var temp = inputTexture;
        inputTexture = outputTexture;
        outputTexture = temp;
        if (!outputTexture) {
            outputTexture = this.texturePool.pop();
            if(!outputTexture) {
                outputTexture = new PIXI.FilterTexture(this.gl, textureSize.width, textureSize.height);
            }
            outputTexture.resize(textureSize.width, textureSize.height);
        }

        gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);

        gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._filterSelfUVArray);

        var filterPass = filterBlock.filterPasses[i];


        // now set the uvs..
        var extraAttribute = filterPass.extraAttribute;
        if (extraAttribute) {
            gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
            for (var idx = 0; idx < extraAttribute.length; ++idx) {
                var filterTexture = filterPass.uniforms[extraAttribute[idx].texture].value;
                if (!filterTexture)
                    continue;
                var spriteUVS = filterTexture._uvs;
                this.uvArray[8 * (idx + 1)] = spriteUVS.x0;
                this.uvArray[8 * (idx + 1) + 1] = spriteUVS.y0;
                this.uvArray[8 * (idx + 1) + 2] = spriteUVS.x1;
                this.uvArray[8 * (idx + 1) + 3] = spriteUVS.y1;
                this.uvArray[8 * (idx + 1) + 4] = spriteUVS.x3;
                this.uvArray[8 * (idx + 1) + 5] = spriteUVS.y3;
                this.uvArray[8 * (idx + 1) + 6] = spriteUVS.x2;
                this.uvArray[8 * (idx + 1) + 7] = spriteUVS.y2;
            }
            gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray);
        }

        gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer );

        // set texture
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture);

        // draw texture..
        this.applyFilterPass(filterPass, new qc.Rectangle(0, 0, textureSize.width, textureSize.height), textureSize.width, textureSize.height, tempTexture, textureSize.width, textureSize.height);
    }
    if (enabledStencilTest) {
        gl.enable(gl.STENCIL_TEST);
    }
    gl.enable(gl.BLEND);
    gl.viewport(0, 0, this.width * this.renderSession.resolution, this.height * this.renderSession.resolution);
    gl.bindFramebuffer(gl.FRAMEBUFFER, this.buffer);

    gl.colorMask(true, true, true, true);
    return outputTexture;
};

/**
 * Applies the filter to the specified area.
 *
 * @method applyFilterPass
 * @param filter {AbstractFilter} the filter that needs to be applied
 * @param filterArea {Texture} TODO - might need an update
 * @param width {Number} the horizontal range of the filter
 * @param height {Number} the vertical range of the filter
 */
PIXI.WebGLFilterManager.prototype.applyFilterPass = function(filter, filterArea, width, height, tempTexture, buffWidth, buffHeight)
{
    // use program
    var gl = this.gl;
    var shader = filter.shaders[gl.id];

    if(!shader)
    {
        shader = new PIXI.PixiShader(gl);

        shader.fragmentSrc = filter.fragmentSrc;
        if (filter.vertexSrc) {
            shader.vertexSrc = filter.vertexSrc;
        }
        shader.uniforms = filter.uniforms;
        if (filter.extraAttribute) {
            shader.extraAttribute = filter.extraAttribute;
        }
        shader.init();

        filter.shaders[gl.id] = shader;
    }

    // set the shader
    this.renderSession.shaderManager.setShader(shader);

//    gl.useProgram(shader.program);
    buffWidth = isNaN(buffWidth) ? this.width : buffWidth;
    buffHeight = isNaN(buffHeight) ? this.height : buffHeight;
    gl.uniform2f(shader.projectionVector, width/2, -height/2);
    gl.uniform2f(shader.offsetVector, 0,0);
    if (shader.pixelSize) {
        gl.uniform2f(shader.pixelSize, 1/buffWidth, 1/buffHeight);
    }

    if(filter.uniforms.dimensions)
    {
        filter.uniforms.dimensions.value[0] = buffWidth;//width;
        filter.uniforms.dimensions.value[1] = buffHeight;//height;
        filter.uniforms.dimensions.value[2] = this.vertexArray[0];
        filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height;
    }
    shader.syncUniforms();

    // modify by chenqx
    if (shader.uSourceSampler && tempTexture) {
        var currTextureCount = shader.textureCount++;
        gl.activeTexture(gl['TEXTURE' + currTextureCount]);
        gl.bindTexture(gl.TEXTURE_2D, tempTexture.texture);
        gl.uniform1i(shader.uSourceSampler, currTextureCount);
    }
    // modify end

    gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
    gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0);

    var oneTextureLen = 32;
    var extraAttribute = filter.extraAttribute;
    gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
    gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0);
    if (extraAttribute) {
        for (var idx = 0; idx < extraAttribute.length; ++idx) {
            gl.vertexAttribPointer(shader[extraAttribute[idx].name], 2, gl.FLOAT, false, 0, (idx + 1) * 32);
        }
    }

    gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
    gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);

    // draw the filter...
    gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 );

    this.renderSession.drawCount++;
};
// @hackpp PIXI.WebGLMaskManager 中 popMask和pushMask不匹配的问题
PIXI.WebGLMaskManager.prototype.popMask = function(maskData, renderSession)
{
    var gl = this.gl;
    if(!maskData._webGL[gl.id].data.length)return;
    renderSession.stencilManager.popStencil(maskData, maskData._webGL[gl.id].data[0], renderSession);
};
// 本功能待提供
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 将宽度除去分辨率
 * @hackpp
 */
Object.defineProperty(Phaser.Text.prototype, 'width', {
    get: function() {

        if(this.dirty)
        {
            this.updateText();
            this.dirty = false;
        }

        return this.scale.x * this.texture.frame.width / this.resolution;
    },
    set: function(value) {
        this.scale.x = value / this.texture.frame.width;
        this._width = value;
    }
});

/**
 * 将高度除去分辨率
 * @hackpp
 */
Object.defineProperty(Phaser.Text.prototype, 'height', {
    get: function() {

        if(this.dirty)
        {
            this.updateText();
            this.dirty = false;
        }


        return  this.scale.y * this.texture.frame.height / this.resolution;
    },
    set: function(value) {
        this.scale.y = value / this.texture.frame.height;
        this._height = value;
    }
});

// text 缩放导致的 baseTexture 脏更新的间隔时间
var textScaleDirtyInterval = 200;

/**
 * Renders the object using the Canvas renderer
 *
 * @method _renderCanvas
 * @param renderSession {RenderSession}
 * @private
 */
var oldRenderCanvas = PIXI.Sprite.prototype._renderCanvas;
var oldRenderWebGL = PIXI.Sprite.prototype._renderWebGL;

var createFunc = function(baseRenderFunc){
    return function(renderSession) {

        if(this.dirty)
        {
            this.updateText();
            this.getWorldTransform();
            this.dirty = false;
        }
        else {
            var gameObject = this._nqc;
            var worldScale = gameObject.getWorldScale();
            var preWorldScale = gameObject._preWorldScale;
            var fixedTime = gameObject.game.time.fixedTime;

            if (!preWorldScale) {
                // 初始化上一帧世界缩放
                gameObject._preWorldScale = new qc.Point(worldScale.x, worldScale.y);
                gameObject._worldScaleChangeTime = fixedTime;
            }
            else if (preWorldScale.x !== worldScale.x || preWorldScale.y !== worldScale.y) {
                // 世界缩放发生变更，记录下来
                preWorldScale.x = worldScale.x;
                preWorldScale.y = worldScale.y;
                gameObject._worldScaleChangeTime = fixedTime;
                if (gameObject.scaleDirtyInterval === 0) {
                    this.updateText();
                    this.getWorldTransform();
                    this.dirty = false;
                }
            }
            else {
                // 世界缩放没任何变化，尝试更新 text canvas
                var textWorldScale = this._worldScale;
                var dirtyInterval = gameObject.scaleDirtyInterval || textScaleDirtyInterval;
                if (fixedTime - gameObject._worldScaleChangeTime > dirtyInterval &&
                    (!textWorldScale || worldScale.x !== textWorldScale.x || worldScale.y !== textWorldScale.y)) {
                    // 不一致，且时间足够长，需要更新
                    this.updateText();
                    this.getWorldTransform();
                    this.dirty = false;
                }
            }
        }

        var canvasDownScale = this._canvasDownScale;
        if (!canvasDownScale) { baseRenderFunc.call(this, renderSession); return; }

        var matrix = this.worldTransform;
        var a = matrix.a;
        var b = matrix.b;
        var c = matrix.c;
        var d = matrix.d;
        var x = canvasDownScale.x;
        var y = canvasDownScale.y;
        matrix.a *= x;
        matrix.b *= x;
        matrix.c *= y;
        matrix.d *= y;

        // 调用原始实现
        baseRenderFunc.call(this, renderSession);

        matrix.a = a;
        matrix.b = b;
        matrix.c = c;
        matrix.d = d;
    }
};

// hack phaser 绘制 text 逻辑
PIXI.Text.prototype._renderCanvas = (function(){ return createFunc(oldRenderCanvas); })();
PIXI.Text.prototype._renderWebGL = (function(){ return createFunc(oldRenderWebGL); })();

/*
 * 当使用 UIText 时，Phaser.Text.prototype.updateText 和 determineFontProperties 两个方法
 * 在 UIText.js 中有被重载，详情见 UIText.js 文件
 */

/**
 * Updates a line of text.
 * @hackpp
 */
Phaser.Text.prototype.updateLine = function (line, x, y, context, strokeThickness, defaultFill, defaultStroke,
                                             lineHeight, gradientFillStyle, UCAndroid) {

    for (var i = 0; i < line.length; i++)
    {
        var letter = line[i];

        if (this.colors[this._charCount])
        {
            if (this.colors[this._charCount] === 'defaultColor') {
                context.fillStyle = defaultFill;
                context.strokeStyle = defaultStroke;
            }
            else {
                context.fillStyle = this.colors[this._charCount];
                context.strokeStyle = this.colors[this._charCount];
            }
        }

        var storeFillStyle = context.fillStyle;
        var storeStrokeStyle = context.strokeStyle;

        if (this.style.stroke && strokeThickness) {
            context.strokeStyle = this.style.stroke;
            context.strokeText(letter, x, y);
        }

        context.fillStyle = storeFillStyle;
        context.strokeStyle = storeStrokeStyle;

        var letterWidth = context.measureText(letter).width;
        if (this.style.fill)
        {
            if (gradientFillStyle === storeFillStyle && UCAndroid) {
                context.save();
                context.fillStyle = 'black';
                context.fillText(letter, x, y);
                context.globalCompositeOperation = 'source-in';
                context.fillStyle = gradientFillStyle;
                context.beginPath();
                context.rect(x, y - lineHeight/2, letterWidth, lineHeight);
                context.clip();
                context.fillRect(x, y - lineHeight/2, letterWidth, lineHeight);
                context.restore();
                if (qc.__IS_ANDROID_UC) {
                    var tempCanvas = qc._tempCanvas;
                    if (!tempCanvas) {
                        tempCanvas = qc._tempCanvas = document.createElement('canvas');
                        tempCanvas.width = 128;
                        tempCanvas.height = 128;
                    }
                    context.drawImage(tempCanvas, 0, 0, 2, 2, -5, -5, 1, 1);
                }
            }
            else {
                context.fillText(letter, x, y);
            }
        }
        x += letterWidth;

        this._charCount++;
    }
};

/**
 * 获取指定文字的宽度
 * @param {string} str
 * @returns {Number}
 * @private
 */
Phaser.Text.prototype._getWorldWidth = function(str) {
    if (str.length > 0 && str[str.length - 1] === '\n') {
        var word = str.substr(0,str.length - 1);
        return this.context.measureText(word).width;
    }
    return this.context.measureText(str).width;
};
/**
 * 判断是否为字母
 * @returns {boolean}
 * @private
 */
Phaser.Text.prototype._isLetter = function(letter){
    // TODO 暂时不做单词判断 以后在富文本中处理
    return false;
    if (( letter < "a" || letter > "z" ) && ( letter < "A" || letter > "Z" )){
        return false;
    }
    return true;
};

/**
 * hack text 的 runWordWrap 防止wrap的时候自动添加空格的bug
 * @hackpp
 */
Phaser.Text.prototype.runWordWrap = function (text, width) {
    var spaceWidth = width;
    var lines = [];
    var startPos = 0;
    var isEnter = false;
    for (var i = 0; i < text.length;) {
        // 换行符
        if (text[i] === '\n') {
            var str = text.substr(startPos, i - startPos + 1);
            // 如果只有一个回车符 并且不是第一行 则将回车符加载行尾
            if (str.length === 1 && lines.length > 0 && !isEnter) {
                lines[lines.length - 1] += '\n';
            }
            else
                lines.push(str);
            i++;
            startPos = i;
            isEnter = true;
        }
        else {
            isEnter = false;
            var startletter = -1;
            while (i < text.length) {
                // 判断是否是英文
                var c = text.charAt(i);
                if (this._isLetter(c)) {
                    if (startletter === -1)
                        startletter = i;
                    i++;
                }
                else {
                    if (startletter >= 0) i--;
                    break;
                }
            }
            if (i === text.length) i--;
            var worldWidth = this._getWorldWidth(text.substr(startPos, i - startPos + 1));
            if (worldWidth > spaceWidth) {
                // 如果英文单词为第一个则全部显示
                if (startletter >= 0 && startletter === startPos) {
                    var str = text.substr(startPos, i - startPos + 1);
                    lines.push(str);
                    i++;
                    startPos = i;
                }
                else if(startletter >= 0) {
                    //如果单词是最后出现则下一行显示
                    var len = startletter - startPos;
                    var str = text.substr(startPos, len);
                    lines.push(str);
                    i = startPos + len;
                    startPos = i;
                }
                else {
                    // 如果只有一个文字 显示
                    if (i - startPos === 0) {
                        var str = text.substr(startPos, i - startPos + 1);
                        lines.push(str);
                        i++;
                        startPos = i;
                    }
                    else {
                        var str = text.substr(startPos, i - startPos);
                        lines.push(str);
                        startPos = i;
                    }
                }
            }
            else {
                i++;
                if (i === text.length) {
                    var str = text.substr(startPos, i - startPos + 1);
                    lines.push(str);
                }
            }
        }
    }
    return lines;
};

/**
 * hackpp
 * 覆盖掉原来phaser的帧调度
 */

/**
 * preUpdate 时会初始化一个位置信息，需要设置 transform 为脏
 * @returns {boolean}
 */
Phaser.Text.prototype.preUpdate = function () {
    var qc = this._qc;
    if (qc && !qc.static) {
        if (qc.preUpdate) qc.preUpdate();

        // 脚本调度
        var scripts = qc.scripts;
        var i = scripts.length;
        while (i--) {
            var script = scripts[i];
            if (!script || !script._enable || !script.__hadUpdateOrRender || !script.preUpdate) continue;

            // 如果当前处于editor模式，并且本脚本没有说明是在editor模式下运行，就不要调度了
            if (qc.game.device.editor === true && script.runInEditor !== true) continue;

            // 节点在脚本中析构了，就不继续调度了
            if (!this.visible) return;

            // 调度之
            script.preUpdate();
        }
    }

    if (this.fresh)
    {
        this.world.setTo(this.parent.position.x + this.position.x, this.parent.position.y + this.position.y);
        this.worldTransform.inUpdate = true;
        this.worldTransform.tx = this.world.x;
        this.worldTransform.ty = this.world.y;
        this._isNotNeedCalcTransform = false;
        this.previousPosition.set(this.world.x, this.world.y);
        this.previousRotation = this.rotation;

        this.fresh = false;
        return false;
    }

    this.previousPosition.set(this.world.x, this.world.y);
    this.previousRotation = this.rotation;

    var i = this.children.length;
    while (i--)
    {
        if (this.children[i].visible) {
            this.children[i].preUpdate();
        }
        else {
            this.children[i].renderOrderID = -1;
        }
    }
    return true;
};

/**
 * Override this function to handle any special update requirements.
 *
 * @method Phaser.Text#update
 * @protected
 */
Phaser.Text.prototype.update = function() {
    var qc = this._qc;
    if (qc && !qc.static) {
        if (qc.preUpdate) qc.preUpdate();

        // 脚本调度
        var scripts = qc.scripts;
        var i = scripts.length;
        while (i--) {
            var script = scripts[i];
            if (!script || !script._enable || !script.__hadUpdateOrRender || !script.preUpdate) continue;

            // 如果当前处于editor模式，并且本脚本没有说明是在editor模式下运行，就不要调度了
            if (qc.game.device.editor === true && script.runInEditor !== true) continue;

            // 节点在脚本中析构了，就不继续调度了
            if (!this.visible) return;

            // 调度之
            script.preUpdate();
        }
    }
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */


Phaser.BitmapText.prototype._isLetter = Phaser.Text.prototype._isLetter;
Phaser.BitmapText.prototype.runWordWrap = Phaser.Text.prototype.runWordWrap;

/**
 * 获取指定文字的宽度
 * @param {string} str
 * @returns {Number}
 * @private
 */
Phaser.BitmapText.prototype._getWorldWidth = function(str) {
    var width = 0;
    var data = PIXI.BitmapText.fonts[this.fontName];
    if (!data || !str) return width;
    var scale = this.fontSize / data.size;
    var prevCharCode = null;
    for (var i = 0; i < str.length; i++) {
        var charCode = str.charCodeAt(i);
        var charData = data.chars[charCode];
        if (!charData) continue;
        if (prevCharCode && charData.kerning[prevCharCode]) {
            width += charData.kerning[prevCharCode];
        }
        width += charData.xAdvance;
        prevCharCode = charCode;
    }
    return width * scale;
};

/**
 * hack updateText - 解决没有设置字体的时候会崩溃
 */
PIXI.BitmapText.prototype.updateText = function()
{
    var text = this.text;
    // @hackapp
    if (!this._nqc) return;
    var data = PIXI.BitmapText.fonts[this.fontName];
    if (!data) return;

    var pos = new PIXI.Point();
    var prevCharCode = null;
    var chars = [];
    var maxLineWidth = 0;
    var lineWidths = [];
    var line = 0;
    var scale = this.fontSize / data.size;
    var lastSpace = 0;

    var startPos = 0;
    var lines = [];

    if (this._nqc.wrap) {
        text = text.replace(/\r/ig,"").replace(/\n/ig,"\n");
        var lines = this.runWordWrap(text, this._nqc.width);
    }
    else {
        var lines = text.split(/(?:\r\n|\r|\n)/);
        for (var i = 0; i < lines.length - 1; i++) {
            lines[i] += '\n';
        }
    }

    pos.x = 0;
    pos.y = 0;
    for (var i = 0; i < lines.length; i++) {
        pos.x = 0;
        prevCharCode = null;
        var world = lines[i];
        for ( var j = 0; j < world.length; j++) {
            var charCode = world.charCodeAt(j);
            var charData = data.chars[charCode];
            if (!charData) continue;

            if (prevCharCode && charData.kerning[prevCharCode]) {
                pos.x += charData.kerning[prevCharCode];
            }

            chars.push({texture:charData.texture, line: i, charCode: charCode, position: new PIXI.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)});

            pos.x += charData.xAdvance;

            prevCharCode = charCode;
        }
        lineWidths.push(pos.x);
        maxLineWidth = Math.max(maxLineWidth, pos.x);
        pos.y += data.lineHeight;
    }

    var lineAlignOffsets = [];

    for (i = 0; i <= lines.length; i++)
    {
        var alignOffset = 0;

        if (this.style.align === 'right')
        {
            alignOffset = maxLineWidth - lineWidths[i];
        }
        else if (this.style.align === 'center')
        {
            alignOffset = (maxLineWidth - lineWidths[i]) / 2;
        }

        lineAlignOffsets.push(alignOffset);
    }

    var lenChildren = this.children.length;
    var lenChars = chars.length;
    var tint = this.tint || 0xFFFFFF;

    this.textWidth = maxLineWidth * scale;
    this.textHeight = data.lineHeight * scale;

    var ax = this.textWidth * this.anchor.x;
    var ay = this.textHeight * this.anchor.y;

    for (i = 0; i < lenChars; i++)
    {
        var c = i < lenChildren ? this.children[i] : this._pool.pop(); // get old child if have. if not - take from pool.

        if (c) c.setTexture(chars[i].texture); // check if got one before.
        else c = new PIXI.Sprite(chars[i].texture); // if no create new one.

        c.position.x = ((chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale) - ax;
        // @hackapp
        c.position.y = Math.floor((chars[i].position.y * scale) - ay);

        c.scale.x = c.scale.y = scale;
        c.tint = tint;
        if (!c.parent) this.addChild(c);
    }

    //  Remove unnecessary children and put them into the pool
    while (this.children.length > lenChars)
    {
        var child = this.getChildAt(this.children.length - 1);
        this._pool.push(child);
        this.removeChild(child);
    }

    this._nqc.textRealWidth = this.textWidth;
    this._nqc.textRealHeight = this.textHeight;
    this.realWidth = this.textWidth;
    this.realHeight = this.textHeight;

    // @hackapp
    this._nqc._adjustTextPos(this.textWidth, this.textHeight);
};

/**
 * hackpp
 * 覆盖掉原来phaser的帧调度
 */

/**
 * Automatically called by World.preUpdate.
 *
 * @method
 * @memberof Phaser.BitmapText
 * @return {boolean} True if the BitmapText was rendered, otherwise false.
 */
Phaser.BitmapText.prototype.preUpdate = function () {
    var qc = this._qc;
    if (qc && !qc.static) {
        if (qc.preUpdate) qc.preUpdate();

        // 脚本调度
        var scripts = qc.scripts;
        var i = scripts.length;
        while (i--) {
            var script = scripts[i];
            if (!script || !script._enable || !script.__hadUpdateOrRender || !script.preUpdate) continue;

            // 如果当前处于editor模式，并且本脚本没有说明是在editor模式下运行，就不要调度了
            if (qc.game.device.editor === true && script.runInEditor !== true) continue;

            // 节点在脚本中析构了，就不继续调度了
            if (!this.visible) return;

            // 调度之
            script.preUpdate();
        }
    }

    // 物理调度
    if (this.fresh) {
        if (qc) qc._isTransformDirty = true;
        this.fresh = false;
        return false;
    }
    this.previousPosition.set(this.world.x, this.world.y);
    this.previousRotation = this.rotation;

    // 主调度
    this.previousPosition.set(this.world.x, this.world.y);
    this.previousRotation = this.rotation;
    this.world.setTo(this.game.camera.x + this.worldTransform.tx, this.game.camera.y + this.worldTransform.ty);
    this.renderOrderID = this.game.stage.currentRenderOrderID++;

    var i = this.children.length;
    while (i--)
    {
        if (this.children[i].visible) {
            this.children[i].preUpdate();
        }
        else {
            this.children[i].renderOrderID = -1;
        }
    }
    return true;
};

/**
 * Automatically called by World.preUpdate.
 * @method Phaser.BitmapText.prototype.postUpdate
 */
Phaser.BitmapText.prototype.postUpdate = function () {
    //  Fixed to Camera?
    if (this.fixedToCamera)
    {
        this.x = this.game.camera.view.x + this.cameraOffset.x;
        this.y = this.game.camera.view.y + this.cameraOffset.y;
    }

    var qc = this._qc;
    if (qc && !qc.static) {
        if (qc.postUpdate) qc.postUpdate();

        // 脚本调度
        var scripts = qc.scripts;
        var i = scripts.length;
        while (i--) {
            var script = scripts[i];
            if (!script || !script._enable || !script.__hadUpdateOrRender || !script.postUpdate) continue;

            // 如果当前处于editor模式，并且本脚本没有说明是在editor模式下运行，就不要调度了
            if (qc.game.device.editor === true && script.runInEditor !== true) continue;

            // 调度之
            script.postUpdate();

            // 节点在脚本中析构了，就不继续调度了
            if (!this.visible) return;
        }
    }
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * hackpp
 * 覆盖掉原来phaser的帧调度
 */

/**
 * Automatically called by World.preUpdate.
 *
 * @method
 * @memberof Phaser.Graphics
 * @return {boolean} True if the Graphics was rendered, otherwise false.
 */
Phaser.Graphics.prototype.preUpdate = function() {
    var qc = this._qc;
    if (qc && !qc.static) {
        if (qc.preUpdate) qc.preUpdate();

        // 脚本调度
        var scripts = qc.scripts;
        var i = scripts.length;
        while (i--) {
            var script = scripts[i];
            if (!script || !script._enable || !script.__hadUpdateOrRender || !script.preUpdate) continue;

            // 如果当前处于editor模式，并且本脚本没有说明是在editor模式下运行，就不要调度了
            if (qc.game.device.editor === true && script.runInEditor !== true) continue;

            // 节点在脚本中析构了，就不继续调度了
            if (!this.visible) return;

            // 调度之
            script.preUpdate();
        }
    }

    if (this.fresh) {
        this.world.setTo(this.parent.position.x + this.position.x, this.parent.position.y + this.position.y);
        this.worldTransform.tx = this.world.x;
        this.worldTransform.ty = this.world.y;

        this.previousPosition.set(this.world.x, this.world.y);
        this.previousRotation = this.rotation;

        if (qc)
            qc._isTransformDirty = true;
        this.fresh = false;
        return false;
    }
    this.previousPosition.set(this.world.x, this.world.y);
    this.previousRotation = this.rotation;

    // 主调度
    this.previousPosition.set(this.world.x, this.world.y);
    this.previousRotation = this.rotation;
    this.world.setTo(this.game.camera.x + this.worldTransform.tx, this.game.camera.y + this.worldTransform.ty);
    this.renderOrderID = this.game.stage.currentRenderOrderID++;

    var i = this.children.length;
    while (i--)
    {
        if (this.children[i].visible) {
            this.children[i].preUpdate();
        }
        else {
            this.children[i].renderOrderID = -1;
        }
    }
    return true;
};

/**
 * Override this method in your own custom objects to handle any update requirements.
 * It is called immediately after `preUpdate` and before `postUpdate`.
 * Remember if this Game Object has any children you should call update on those too.
 *
 * @method
 */
Phaser.Graphics.prototype.update = function() {
    var qc = this._qc;
    if (qc && !qc.static) {
        if (qc.update) qc.update();

        // 脚本调度
        var scripts = qc.scripts;
        var i = scripts.length;
        while (i--) {
            var script = scripts[i];
            if (!script || !script._enable || !script.__hadUpdateOrRender || !script.update) continue;

            // 如果当前处于editor模式，并且本脚本没有说明是在editor模式下运行，就不要调度了
            if (qc.game.device.editor === true && script.runInEditor !== true) continue;

            // 调度之
            script.update();

            // 节点在脚本中析构了，就不继续调度了
            if (!this.visible) return;
        }
    }

    var i = this.children.length;
    while (i--)
    {
        if (this.children[i].visible) {
            this.children[i].update();
        }
    }
};

/**
 * Internal method called by the World postUpdate cycle.
 *
 * @method
 * @protected
 */
Phaser.Graphics.prototype.postUpdate = function() {
    if (this.key instanceof Phaser.BitmapData)
    {
        this.key.render();
    }

    var qc = this._qc;
    if (qc && !qc.static) {
        if (qc.postUpdate) qc.postUpdate();

        // 脚本调度
        var scripts = qc.scripts;
        var i = scripts.length;
        while (i--) {
            var script = scripts[i];
            if (!script || !script._enable || !script.__hadUpdateOrRender || !script.postUpdate) continue;

            // 如果当前处于editor模式，并且本脚本没有说明是在editor模式下运行，就不要调度了
            if (qc.game.device.editor === true && script.runInEditor !== true) continue;

            // 调度之
            script.postUpdate();

            // 节点在脚本中析构了，就不继续调度了
            if (!this.visible) return;
        }
    }

    var i = this.children.length;
    while (i--)
    {
        if (this.children[i].visible)
            this.children[i].postUpdate();
    }
};
/**
* @author       Richard Davey <rich@photonstorm.com>
* @copyright    2015 Photon Storm Ltd.
* @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/

/**
 * 重载updateTransform方法，保证粒子对象不收其父亲的移动影响
 */
Phaser.Particle.prototype.updateTransform = function() {
    if (!this.parent || !this.visible) return;

    // create some matrix refs for easy access
    var pt = this._worldTransform;
    if (!pt) return;
    var wt = this.worldTransform;

    // temporary matrix variables
    var a, b, c, d, tx, ty;

    // so if rotation is between 0 then we can simplify the multiplication process..
    if (this.rotation % PIXI.PI_2)
    {
        // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes
        if (this.rotation !== this.rotationCache)
        {
            this.rotationCache = this.rotation;
            this._sr = Math.sin(this.rotation);
            this._cr = Math.cos(this.rotation);
        }

        // get the matrix values of the displayobject based on its transform properties..
        a  =  this._cr * this.scale.x;
        b  =  this._sr * this.scale.x;
        c  = -this._sr * this.scale.y;
        d  =  this._cr * this.scale.y;
        tx =  this.position.x;
        ty =  this.position.y;

        // check for pivot.. not often used so geared towards that fact!
        if (this.pivot.x || this.pivot.y)
        {
            tx -= this.pivot.x * a + this.pivot.y * c;
            ty -= this.pivot.x * b + this.pivot.y * d;
        }

        // concat the parent matrix with the objects transform.
        wt.a  = a  * pt.a + b  * pt.c;
        wt.b  = a  * pt.b + b  * pt.d;
        wt.c  = c  * pt.a + d  * pt.c;
        wt.d  = c  * pt.b + d  * pt.d;
        wt.tx = tx * pt.a + ty * pt.c + pt.tx;
        wt.ty = tx * pt.b + ty * pt.d + pt.ty;
    }
    else
    {
        // lets do the fast version as we know there is no rotation..
        a  = this.scale.x;
        d  = this.scale.y;

        tx = this.position.x - this.pivot.x * a;
        ty = this.position.y - this.pivot.y * d;

        wt.a  = a  * pt.a;
        wt.b  = a  * pt.b;
        wt.c  = d  * pt.c;
        wt.d  = d  * pt.d;
        wt.tx = tx * pt.a + ty * pt.c + pt.tx;
        wt.ty = tx * pt.b + ty * pt.d + pt.ty;
    }

    // multiply the alphas..
    this.worldAlpha = this.alpha;

    //  Custom callback?
    if (this.transformCallback)
    {
        this.transformCallback.call(this.transformCallbackContext, wt, pt);
    }
};

/**
 * Updates the Particle scale or alpha if autoScale and autoAlpha are set.
 *
 * @method Phaser.Particle#update
 * @memberof Phaser.Particle
 */
Phaser.Particle.prototype.update = function() {
    var qc = this._qc;
    if (qc && !qc.static) {
        if (qc.update) qc.update();

        // 脚本调度
        var scripts = qc.scripts;
        var i = scripts.length;
        while (i--) {
            var script = scripts[i];
            if (!script || !script._enable || !script.__hadUpdateOrRender || !script.update) continue;

            // 如果当前处于editor模式，并且本脚本没有说明是在editor模式下运行，就不要调度了
            if (qc.game.device.editor === true && script.runInEditor !== true) continue;

            // 调度之
            script.update();

            // 节点在脚本中析构了，就不继续调度了
            if (!this.visible) return;
        }
    }

    if (this.autoScale)
    {
        this._s--;

        if (this._s >= 0)
        {
            this.scale.set(this.scaleData[this._s].x, this.scaleData[this._s].y);
        }
        else
        {
            this.autoScale = false;
        }
    }

    if (this.autoAlpha)
    {
        this._a--;

        if (this._a >= 0)
        {
            this.alpha = this.alphaData[this._a].v;
        }
        else
        {
            this.autoAlpha = false;
        }
    }
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * hackpp
 * 覆盖掉原来phaser的帧调度
 */

/**
 * Automatically called by World.preUpdate.
 * 废除了原来的边界裁切
 *
 * @method
 * @memberof Phaser.Sprite
 * @return {boolean} True if the Sprite was rendered, otherwise false.
 */
Phaser.Sprite.prototype.preUpdate = function() {
    var qc = this._qc;
    if (qc && !qc.static) {
        if (qc.preUpdate) qc.preUpdate();

        // 脚本调度
        var scripts = qc.scripts;
        var i = scripts.length;
        while (i--) {
            var script = scripts[i];
            if (!script || !script._enable || !script.__hadUpdateOrRender || !script.preUpdate) continue;

            // 如果当前处于editor模式，并且本脚本没有说明是在editor模式下运行，就不要调度了
            if (qc.game.device.editor === true && script.runInEditor !== true) continue;

            // 节点在脚本中析构了，就不继续调度了
            if (!this.visible) return;

            // 调度之
            script.preUpdate();
        }
    }

    // 物理调度
    if (this.fresh) {
        this.world.setTo(this.parent.position.x + this.position.x, this.parent.position.y + this.position.y);
        this.worldTransform.tx = this.world.x;
        this.worldTransform.ty = this.world.y;

        this.previousPosition.set(this.world.x, this.world.y);
        this.previousRotation = this.rotation;

        if (this.body) {
            this.body.preUpdate();
        }

        if (qc)
            qc._isTransformDirty = true;
        this.fresh = false;
        return false;
    }
    this.previousPosition.set(this.world.x, this.world.y);
    this.previousRotation = this.rotation;

    // 生命周期调度
    if (this.lifespan > 0)
    {
        this.lifespan -= this.game.time.physicsElapsedMS;
        if (this.lifespan <= 0)
        {
            this.kill();
            return false;
        }
    }

    // 主调度
    this.previousPosition.set(this.world.x, this.world.y);
    this.previousRotation = this.rotation;
    this.world.setTo(this.game.camera.x + this.worldTransform.tx, this.game.camera.y + this.worldTransform.ty);
    this.renderOrderID = this.game.stage.currentRenderOrderID++;

    if (this.animations)
    {
        // 动作驱动
        this.animations.update();
    }

    if (this.body)
    {
        // 物理驱动
        this.body.preUpdate();
    }

    var i = this.children.length;
    while (i--)
    {
        if (this.children[i].visible) {
            this.children[i].preUpdate();
        }
        else {
            this.children[i].renderOrderID = -1;
        }
    }
    return true;
};

/**
 * Override this method in your own custom objects to handle any update requirements.
 * It is called immediately after `preUpdate` and before `postUpdate`.
 * Remember if this Game Object has any children you should call update on those too.
 *
 * @method
 */
Phaser.Sprite.prototype.update = function() {
    var qc = this._qc;
    if (qc && !qc.static) {
        if (qc.update) qc.update();

        // 脚本调度
        var scripts = qc.scripts;
        var i = scripts.length;
        while (i--) {
            var script = scripts[i];
            if (!script || !script._enable || !script.__hadUpdateOrRender || !script.update) continue;

            // 如果当前处于editor模式，并且本脚本没有说明是在editor模式下运行，就不要调度了
            if (qc.game.device.editor === true && script.runInEditor !== true) continue;

            // 调度之
            script.update();

            // 节点在脚本中析构了，就不继续调度了
            if (!this.visible) return;
        }
    }

    var i = this.children.length;
    while (i--)
    {
        if (this.children[i].visible) {
            this.children[i].update();
        }
    }
};

/**
 * Internal method called by the World postUpdate cycle.
 *
 * @method
 * @protected
 */
Phaser.Sprite.prototype.postUpdate = function() {
    if (this.key instanceof Phaser.BitmapData)
    {
        this.key.render();
    }

    if (this.body)
    {
        this.body.postUpdate();
    }

    if (this.fixedToCamera)
    {
        this.x = this.game.camera.view.x + this.cameraOffset.x;
        this.y = this.game.camera.view.y + this.cameraOffset.y;
    }

    var qc = this._qc;
    if (qc && !qc.static) {
        if (qc.postUpdate) qc.postUpdate();

        // 脚本调度
        var scripts = qc.scripts;
        var i = scripts.length;
        while (i--) {
            var script = scripts[i];
            if (!script || !script._enable || !script.__hadUpdateOrRender || !script.postUpdate) continue;

            // 如果当前处于editor模式，并且本脚本没有说明是在editor模式下运行，就不要调度了
            if (qc.game.device.editor === true && script.runInEditor !== true) continue;

            // 调度之
            script.postUpdate();

            // 节点在脚本中析构了，就不继续调度了
            if (!this.visible) return;
        }
    }

    var i = this.children.length;
    while (i--)
    {
        if (this.children[i].visible)
            this.children[i].postUpdate();
    }
};
/**
 * @author linyw
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * https://github.com/bjorn/tiled/issues/925
 * but I did it this way so that margin could be used as purely a starting offset.
 * Just that now the name is a little confusing..
 *
 * Tiled编辑器（http://www.mapeditor.org/）的margin仅代表左上角的偏移，
 * Phaser解析为常规理解的上下左右都预留空间，以下函数做修正保持和Tiled编辑器一致
 */
Phaser.Tileset.prototype.updateTileData = function (imageWidth, imageHeight) {
    imageWidth -= this.tileMargin;
    imageHeight -= this.tileMargin;

    // May be fractional values
    var rowCount;
    var colCount;
    if (imageHeight > this.tileHeight) {
        rowCount = (imageHeight - this.tileHeight) / (this.tileHeight + this.tileSpacing) + 1;
    } else {
        rowCount = imageHeight / this.tileHeight;
    }
    if (imageWidth > this.tileWidth) {
        colCount = (imageWidth - this.tileWidth) / (this.tileWidth + this.tileSpacing) + 1;
    } else {
        colCount = imageWidth / this.tileWidth;
    }

    if (rowCount % 1 !== 0 || colCount % 1 !== 0)
    {
//   console.warn("Phaser.Tileset - image tile area is not an even multiple of tile size", rowCount, colCount);
    }

    // In Tiled a tileset image that is not an even multiple of the tile dimensions
    // is truncated - hence the floor when calculating the rows/columns.
    rowCount = Math.floor(rowCount);
    colCount = Math.floor(colCount);

    if ((this.rows && this.rows !== rowCount) || (this.columns && this.columns !== colCount))
    {
        console.warn("Phaser.Tileset - actual and expected number of tile rows and columns differ");
    }

    this.rows = rowCount;
    this.columns = colCount;
    this.total = rowCount * colCount;

    this.drawCoords.length = 0;

    var tx = this.tileMargin;
    var ty = this.tileMargin;

    for (var y = 0; y < this.rows; y++)
    {
        for (var x = 0; x < this.columns; x++)
        {
            this.drawCoords.push(tx);
            this.drawCoords.push(ty);
            tx += this.tileWidth + this.tileSpacing;
        }

        tx = this.tileMargin;
        ty += this.tileHeight + this.tileSpacing;
    }

};
/**
 * @author linyw
 * copyright 2015 Qcplay All Rights Reserved.
 */

Phaser.Cache.prototype.getSound = function (key) {
    if (this._sounds[key])
    {
        return this._sounds[key];
    }
    else
    {
        // 关闭警告，qc.Sound构造函数未初始化key，导致audio tag下出警告提示
        //console.warn('Phaser.Cache.getSound: Invalid key: "' + key + '"');
        return null;
    }
};
/**
 * @author linyw
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 解决Android下AudioTag类型声音对象第一时间无法获取正确duration值问题
 */
Phaser.Loader.prototype.loadAudioTag = function (file) {

    var _this = this;

    if (this.game.sound.touchLocked)
    {
        //  If audio is locked we can't do this yet, so need to queue this load request. Bum.
        file.data = new Audio();
        file.data.name = file.key;
        file.data.preload = 'auto';
        file.data.src = this.transformUrl(file.url, file);

        this.fileComplete(file);
    }
    else
    {
        file.data = new Audio();
        file.data.name = file.key;

        var playThroughEvent = function () {
            file.data.removeEventListener('canplaythrough', playThroughEvent, false);
            file.data.onerror = null;
            // Why does this cycle through games?
            Phaser.GAMES[_this.game.id].load.fileComplete(file);
        };
        file.data.onerror = function () {
            file.data.removeEventListener('canplaythrough', playThroughEvent, false);
            file.data.onerror = null;
            _this.fileError(file);
        };

        file.data.preload = 'auto';
        file.data.src = this.transformUrl(file.url, file);
        file.data.addEventListener('canplaythrough', playThroughEvent, false);

        // 屏蔽load()调用，改行代码会导致Android下很多浏览器第一时间无法获取正确duration值
        //file.data.load();
    }

};
/**
 * @author linyw
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 解决在WebAudio模式下，loop循环的音乐在pause之后resume会导致loop失效的问题
 */
Phaser.Sound.prototype.resume = function () {

    if (this.paused && this._sound)
    {
        if (this.usingWebAudio)
        {
            var p = this.position + (this.pausedPosition / 1000);

            this._sound = this.context.createBufferSource();
            this._sound.buffer = this._buffer;

            if (this.externalNode)
            {
                this._sound.connect(this.externalNode);
            }
            else
            {
                this._sound.connect(this.gainNode);
            }

            if (this.loop)
            {
                this._sound.loop = true;
            }

            if (!this.loop && this.currentMarker === '')
            {
                this._sound.onended = this.onEndedHandler.bind(this);
            }

            var duration = this.duration - (this.pausedPosition / 1000);

            if (typeof this._sound.start === 'undefined')
            {
                this._sound.noteGrainOn(0, p, duration);
                //this._sound.noteOn(0); // the zero is vitally important, crashes iOS6 without it
            }
            else
            {
                // 如果是循环播放，则不传入duration，否则即使loop为true，播放到duration后就停止了
                if (this.loop)
                {
                    this._sound.start(0, p);
                }
                else
                {
                    this._sound.start(0, p, duration);
                }
            }
        }
        else
        {
            this._sound.play();
        }

        this.isPlaying = true;
        this.paused = false;
        this.startTime += (this.game.time.time - this.pausedTime);
        this.onResume.dispatch(this);
    }

};



/**
 * @author linyw
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * hack Phaser.SoundManager 中 decode sound 结束回调后，上下文变化导致无法找到 game.cache 而报错
 * Decode a sound by its asset key.
 *
 * @method Phaser.SoundManager#decode
 * @param {string} key - Assets key of the sound to be decoded.
 * @param {Phaser.Sound} [sound] - Its buffer will be set to decoded data.
 */
Phaser.SoundManager.prototype.decode = function (key, sound) {

    sound = sound || null;

    var soundData = this.game.cache.getSoundData(key);

    // console.log(key, 'soundData', soundData);

    if (soundData)
    {
        if (this.game.cache.isSoundDecoded(key) === false)
        {
            this.game.cache.updateSound(key, 'isDecoding', true);

            var that = this;

            this.context.decodeAudioData(soundData, function (buffer) {
                // hack start
                // 上下文已经发生变化，不需要后续的行为
                if (!that.game.cache) return;
                // hack end

                if (buffer)
                {
                    that.game.cache.decodedSound(key, buffer);
                    that.onSoundDecode.dispatch(key, sound);
                }
            });
        }
    }
};


/**
 * iOS9下必须在touchEnd进行unlock
 * Initialises the sound manager.
 * @method Phaser.SoundManager#boot
 * @protected
 */
Phaser.SoundManager.prototype.boot = function () {

    if (this.game.device.iOS && this.game.device.webAudio === false)
    {
        this.channels = 1;
    }

    if (window['PhaserGlobal'])
    {
        //  Check to see if all audio playback is disabled (i.e. handled by a 3rd party class)
        if (window['PhaserGlobal'].disableAudio === true)
        {
            this.usingWebAudio = false;
            this.noAudio = true;
            return;
        }

        //  Check if the Web Audio API is disabled (for testing Audio Tag playback during development)
        if (window['PhaserGlobal'].disableWebAudio === true)
        {
            this.usingWebAudio = false;
            this.usingAudioTag = true;
            this.noAudio = false;
            return;
        }
    }

    if (window['PhaserGlobal'] && window['PhaserGlobal'].audioContext)
    {
        this.context = window['PhaserGlobal'].audioContext;
    }
    else
    {
        if (!!window['AudioContext'])
        {
            try {
                this.context = new window['AudioContext']();
            } catch (error) {
                this.context = null;
                this.usingWebAudio = false;
                this.noAudio = true;
                
                console.error('SoundManager boot error!', error);
                qc.Util.popupError(error.message); 
            }
        }
        else if (!!window['webkitAudioContext'])
        {
            try {
                this.context = new window['webkitAudioContext']();
            } catch (error) {
                this.context = null;
                this.usingWebAudio = false;
                this.noAudio = true;
                
                console.error('SoundManager boot error!', error);
                qc.Util.popupError(error.message); 
            }
        }
    }

    if (!!window['Audio'] && this.context === null)
    {
        this.usingWebAudio = false;
        this.usingAudioTag = true;
        this.noAudio = false;
    }

    if (this.context !== null)
    {
        if (typeof this.context.createGain === 'undefined')
        {
            this.masterGain = this.context.createGainNode();
        }
        else
        {
            this.masterGain = this.context.createGain();
        }

        this.masterGain.gain.value = 1;
        this.masterGain.connect(this.context.destination);
    }

};

/**
 * iOS9下需要设置touchEndCallback为null
 */
Phaser.SoundManager.prototype.unlock = function () {
    if (this.touchLocked === false)
    {
        return;
    }

    //  Global override (mostly for Audio Tag testing)
    if (this.game.device.webAudio === false || (window['PhaserGlobal'] && window['PhaserGlobal'].disableWebAudio === true))
    {
        //  Create an Audio tag?
        this.touchLocked = false;
        this._unlockSource = null;
        this.game.input._qc.touch.callbackContext = null;

        // iOS9下必须在touchEnd进行unlock
        // https://github.com/photonstorm/phaser/commit/f64fc42f3e28c8f02562234ad8d09fd9d49fd24a
        if (this.game.device.iOSVersion > 8) {
            this.game.input._qc.touch.touchEndCallback = null;
        }
        else {
            this.game.input._qc.touch.touchStartCallback = null;
        }
        this.game.input._qc.mouse.callbackContext = null;
        this.game.input._qc.mouse.mouseDownCallback = null;
    }
    else
    {
        // Create empty buffer and play it
        var buffer = this.context.createBuffer(1, 1, 22050);
        this._unlockSource = this.context.createBufferSource();
        this._unlockSource.buffer = buffer;
        this._unlockSource.connect(this.context.destination);

        if (typeof this._unlockSource.start === 'undefined')
        {
            this._unlockSource.noteOn(0);
        }
        else
        {
            this._unlockSource.start(0);
        }
    }
};

/**
 * iOS9下需要设置touchEndCallback为null
 */
Phaser.SoundManager.prototype.update = function () {

    if (this.touchLocked)
    {
        if (this.game.device.webAudio && this._unlockSource !== null)
        {
            if ((this._unlockSource.playbackState === this._unlockSource.PLAYING_STATE || this._unlockSource.playbackState === this._unlockSource.FINISHED_STATE))
            {
                this.touchLocked = false;
                this._unlockSource = null;
                this.game.input._qc.touch.callbackContext = null;
                
                // iOS9下必须在touchEnd进行unlock
                // https://github.com/photonstorm/phaser/commit/f64fc42f3e28c8f02562234ad8d09fd9d49fd24a
                if (this.game.device.iOSVersion > 8) {
                    this.game.input._qc.touch.touchEndCallback = null;
                }
                else {
                    this.game.input._qc.touch.touchStartCallback = null;
                }                
            }
        }
    }

    for (var i = 0; i < this._sounds.length; i++)
    {
        this._sounds[i].update();
    }

    if (this._watching)
    {
        var key = this._watchList.first;

        while (key)
        {
            if (this.game.cache.isSoundDecoded(key))
            {
                this._watchList.remove(key);
            }

            key = this._watchList.next;
        }

        if (this._watchList.total === 0)
        {
            this._watching = false;
            this._watchCallback.call(this._watchContext);
        }
    }

};
/**
 * @author wudm
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * hack phaser transformUrl 方法，如果 url 已经是 http 开头就不需要再加上 baseURL
 * Transforms the asset URL. The default implementation prepends the baseURL.
 *
 * @method Phaser.Loader#transformUrl
 * @protected
 */
Phaser.Loader.prototype.transformUrl = function (url) {
    if (/^http(s|):\/\//i.test(url))
        return url;
    else
        return this.baseURL + url;
};

/**
 * Informs the loader that the given file resource has been fetched and processed;
 * or such a request has failed.
 *
 * @method Phaser.Loader#asyncComplete
 * @private
 * @param {object} file
 * @param {string} [error=''] - The error message, if any. No message implies no error.
 */
Phaser.Loader.prototype.asyncComplete = function (file, errorMessage) {

    if (typeof errorMessage === 'undefined') { errorMessage = ''; }

    file.loaded = true;
    file.error = !!errorMessage;

    // 增加返回错误码的判断
    if (file.requestObject && 
        file.requestObject.status !== 200 &&
        file.requestObject.status !== 304) {
        file.error = true;
    }

    if (errorMessage) {
        file.errorMessage = errorMessage;

        console.warn('Phaser.Loader - ' + file.type + '[' + file.key + ']' + ': ' + errorMessage);
        // debugger;
    }

    this.processLoadQueue();

};


/**
 * Renders the Stage to this canvas view
 *
 * @method render
 * @param stage {Stage} the Stage element to be rendered
 */
PIXI.CanvasRenderer.prototype.render = function(stage)
{
    // remove this line
    // stage.updateTransform();

    this.context.setTransform(1,0,0,1,0,0);

    this.context.globalAlpha = 1;

    this.renderSession.currentBlendMode = PIXI.blendModes.NORMAL;
    this.context.globalCompositeOperation = PIXI.blendModesCanvas[PIXI.blendModes.NORMAL];

    if (navigator.isCocoonJS && this.view.screencanvas)
    {
        this.context.fillStyle = "black";
        this.context.clear();
    }
    
    if (this.clearBeforeRender)
    {
        if (this.transparent)
        {
            this.context.clearRect(0, 0, this.width, this.height);
        }
        else
        {
            this.context.fillStyle = stage.backgroundColorString;
            this.context.fillRect(0, 0, this.width , this.height);
        }
    }
    
    this.renderDisplayObject(stage);

};

/**
 * Renders the stage to its webGL view
 *
 * @method render
 * @param stage {Stage} the Stage element to be rendered
 */
PIXI.WebGLRenderer.prototype.render = function(stage)
{
    // no point rendering if our context has been blown up!
    if (this.contextLost) return;

    // if rendering a new stage clear the batches..
    if (this.__stage !== stage)
    {
        // TODO make this work
        // dont think this is needed any more?
        this.__stage = stage;
    }

    // 干掉这行
    // update the scene graph
    //stage.updateTransform();

    var gl = this.gl;

    // -- Does this need to be set every frame? -- //
    gl.viewport(0, 0, this.width, this.height);

    // make sure we are bound to the main frame buffer
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);

    if (this.clearBeforeRender)
    {
        if (this.transparent)
        {
            gl.clearColor(0, 0, 0, 0);
        }
        else
        {
            gl.clearColor(stage.backgroundColorSplit[0],stage.backgroundColorSplit[1],stage.backgroundColorSplit[2], 1);
        }

        gl.clear (gl.COLOR_BUFFER_BIT);
    }

    this.renderDisplayObject( stage, this.projection );
};
/**
 * @author luohj
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 游戏对象工厂
 *
 * @class qc.GameObjectFactory
 * @constructor
 * @internal
 */
var GameObjectFactory = qc.GameObjectFactory = function(game) {
    /**
     * @property {qc.Game} game - A reference to the currently running Game.
     * @protected
     */
    this.game = game;
};
GameObjectFactory.prototype.constructor = GameObjectFactory;

/*
* 克隆一个对象出来
*/
GameObjectFactory.prototype.clone = function(ob, parent) {
    if (!ob) return null;

    if (ob instanceof Prefab) {
        // 从预制生成
        var a = this.game.serializer.restoreBundle(ob.json, parent);
        a._prefab = ob.uuid;
        return a;
    }

    // 其他情况先序列化再反序列化下
    var context = {};
    var json = this.game.serializer.buildBundle(ob, context);
    json.dependences = this.game.serializer.combineDependence(context);
    var a = this.game.serializer.restoreBundle(json, parent ? parent : ob.parent);
    return a;
};

/**
 * 创建一个空的节点
 *
 * @method qc.GameObjectFactory#node
 */
GameObjectFactory.prototype.node = function(parent, uuid) {
    var node = new Node(new Phaser.Group(this.game.phaser, null), parent, uuid);
    node.name = 'node';
    return node;
};

/**
 * 创建一个UIText
 *
 * @method qc.GameObjectFactory#text
 * @return {qc.UIText} The newly created text object.
 */
GameObjectFactory.prototype.text = function(parent, uuid) {
    return new UIText(this.game, parent, uuid);
};

/**
 * 创建一个UIImage
 *
 * @method qc.GameObjectFactory#image
 * @return {qc.UIImage} The newly created image object.
 */
GameObjectFactory.prototype.image = function(parent, uuid) {
    return new UIImage(this.game, parent, uuid);
};

/**
 * 创建一个按钮
 *
 * @param parent
 * @returns {qc.Button} 创建的按钮对象
 */
GameObjectFactory.prototype.button = function(parent, uuid) {
    return new Button(this.game, parent, uuid);
};

/**
 * 创建一个开关组件
 *
 * @param parent
 * @returns {qc.Toggle}
 */
GameObjectFactory.prototype.toggle = function(parent, uuid) {
    return new Toggle(this.game, parent, uuid);
};

/**
 *  创建一个精灵
 *
 *  @method qc.GameObjectFactory#sprite
 *  @return {qc.Sprite}
 */
GameObjectFactory.prototype.sprite = function(parent, uuid) {
    return new Sprite(this.game, parent, uuid);
};

/**
 *  创建一个声音
 *
 *  @method qc.GameObjectFactory#Sound
 *  @return {qc.Sound}
 */
GameObjectFactory.prototype.sound = function(parent, uuid) {
    return new Sound(this.game, parent, uuid);
};

/**
 * 创建一个滚动窗体
 * @method qc.GameObjectFactory#scrollView
 * @param parent {qc.Node} - 父亲节点
 * @returns {qc.ScrollView}
 */
GameObjectFactory.prototype.scrollView = function(parent, uuid) {
    return new ScrollView(this.game, parent, uuid);
};

/**
 * 创建一个滚动条
 * @method qc.GameObjectFactory#scrollBar
 * @param parent {qc.Node} - 父亲节点
 * @param createSliders {boolean} - 是否创建滑块
 * @returns {qc.ScrollBar}
 */
GameObjectFactory.prototype.scrollBar = function(parent, createSliders, uuid) {
    var scrollBar = new ScrollBar(this.game, parent, uuid);
    var restore = uuid !== undefined;
    if (restore) return scrollBar;

    scrollBar.width = 160;
    scrollBar.height = 20;
    scrollBar.size = 0.2;
    scrollBar.direction = qc.ScrollBar.LEFT_TO_RIGHT;
    if (createSliders) {
        var slidingArea = this.node(scrollBar);
        slidingArea.name = 'slidingArea';
        slidingArea.setAnchor(new qc.Point(0, 0), new qc.Point(1, 1));
        slidingArea.setStretch(0, 0, 0, 0);
        slidingArea.pivotX = slidingArea.pivotY = 0.5;

        var sliders = this.image(slidingArea);
        sliders.name = 'sliders';
        sliders.setAnchor(new qc.Point(0, 0), new qc.Point(1, 1));
        sliders.setStretch(0, 0, 0, 0);
        sliders.pivotX = sliders.pivotY = 0.5;
        scrollBar.sliders = sliders;
        scrollBar.interactive = true;
    }

    return scrollBar;
};

/**
 * 创建一个进度条
 * @method qc.GameObjectFactory#progressBar
 * @param parent {qc.Node} - 父亲节点
 * @param createSliders {boolean} - 是否创建滑块
 * @returns {qc.progressBar}
 */
GameObjectFactory.prototype.progressBar = function(parent, createSliders, uuid) {
    var progressBar = new ProgressBar(this.game, parent, uuid);
    var restore = uuid !== undefined;
    if (restore) return progressBar;

    progressBar.width = 160;
    progressBar.height = 20;
    if (createSliders) {
        var progressArea = this.node(progressBar);
        progressArea.name = 'progressArea';
        progressArea.setAnchor(new qc.Point(0, 0), new qc.Point(1, 1));
        progressArea.setStretch(0, 0, 0, 0);
        progressArea.pivotX = progressArea.pivotY = 0.5;

        var sliders = this.image(progressArea);
        sliders.name = 'sliders';
        sliders.setAnchor(new qc.Point(0, 0), new qc.Point(1, 1));
        sliders.setStretch(0, 0, 0, 0);
        sliders.pivotX = sliders.pivotY = 0.5;
        progressBar.sliders = sliders;
    }

    return progressBar;
};


/**
 * 创建一个滑动条
 * @method qc.GameObjectFactory#progressBar
 * @param parent {qc.Node} - 父亲节点
 * @param createSliders {boolean} - 是否创建滑块
 * @returns {qc.progressBar}
 */
GameObjectFactory.prototype.slider = function(parent, createSliders, uuid) {
    var slider = new Slider(this.game, parent, uuid);
    var restore = uuid !== undefined;
    if (restore) return slider;

    slider.interactive = true;
    slider.width = 160;
    slider.height = 20;
    if (createSliders) {
        var slidingArea = this.node(slider);
        slidingArea.name = 'slidingArea';
        slidingArea.setAnchor(new qc.Point(0, 0), new qc.Point(1, 1));
        slidingArea.setStretch(0, 0, 0, 0);
        slidingArea.pivotX = slidingArea.pivotY = 0.5;

        var sliders = this.image(slidingArea);
        sliders.name = 'sliders';
        sliders.setAnchor(new qc.Point(0.5, 0.5), new qc.Point(0.5, 0.5));
        sliders.setStretch(0, 0, 0, 0);
        sliders.pivotX = sliders.pivotY = 0.5;

        slider.sliders = sliders;
    }

    return slider;
};

/**
 * 创建一个输入框
 * @method qc.GameObjectFactory#inputField
 * @param parent
 * @param uuid
 */
GameObjectFactory.prototype.inputField = function(parent, uuid) {
    return new InputField(this.game, parent, uuid);
};

/**
 * 创建一个粒子发射器
 * @method qc.GameObjectFactory#emitter
 * @param parent
 * @param uuid
 */
GameObjectFactory.prototype.emitter = function(parent, uuid) {
    throw new Error('Particle System is coming.');
};

/**
 * 创建一个瓦片地图
 * @method qc.GameObjectFactory#tilemap
 * @param parent
 * @param uuid
 * @returns {qc.Tilemap}
 */
GameObjectFactory.prototype.tilemap = function(parent, uuid) {
    return new Tilemap(this.game, parent, uuid);
};

/**
 * 创建一个瓦片地图图层
 * @param parent
 * @param uuid
 * @returns {qc.TileLayer}
 */
GameObjectFactory.prototype.tileLayer = function(parent, uuid) {
    return new TileLayer(this.game, parent, uuid);
};

/**
 * 创建一个对象图层
 * @param parent
 * @param uuid
 * @returns {qc.ObjectLayer}
 */
GameObjectFactory.prototype.objectLayer = function(parent, uuid) {
    return new ObjectLayer(this.game, parent, uuid);
};

/**
 * 创建一个DOM对象
 * @param parent
 * @param uuid
 * @returns {qc.Dom}
 */
GameObjectFactory.prototype.dom = function(parent, uuid) {
    return new Dom(this.game, parent, uuid);
};

/**
 * 创建一个Graphics对象
 * @param parent
 * @param uuid
 * @returns {qc.Graphics}
 */
GameObjectFactory.prototype.graphics = function(parent, uuid) {
    return new Graphics(this.game, parent, uuid);
};

/**
 * @author wudm
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * image 对象，一般用于 UI，静态图片显示
 *
 * @class qc.UIImage
 * @extends qc.Node
 * @param {qc.Game} game
 * @constructor
 * @internal
 */
var UIImage = qc.UIImage = function(game, parent, uuid) {
    var phaserImage = new Phaser.Sprite(game.phaser);
    qc.Node.call(this, phaserImage, parent, uuid);

    // 我们需要重载掉 Phaser 的 updateTransform 来支持 skew
    phaserImage.updateTransform = imageUpdateTransform;

    // 需要重载掉 Phaser 的 _renderWebGL 来实现九宫格
    phaserImage._renderWebGL = imageRenderWebGL;

    // 需要重载掉 Phaser 的 _renderCanvas 来实现九宫格
    phaserImage._renderCanvas = imageRenderCanvas;

    // 初始化九宫格边界
    this._borderLeft = 0;
    this._borderRight = 0;
    this._borderTop = 0;
    this._borderBottom = 0;

    this._imageType = qc.UIImage.IMAGE_TYPE_SIMPLE;

    // 初始化默认的名字
    this.name = 'UIImage';
};

UIImage.prototype = Object.create(qc.Node.prototype);
UIImage.prototype.constructor = UIImage;

UIImage.IMAGE_TYPE_SIMPLE = 0;
UIImage.IMAGE_TYPE_SLICED = 1;
UIImage.IMAGE_TYPE_TILED = 2;

Object.defineProperties(UIImage.prototype, {
    /**
     *  获取or设置当前的图片
     *  @property {qc.Atlas} texture
     */
    texture : {
        get : function() {
            return this._texture;
        },
        set : function(value) {
            if (!value) {
                this._texture = null;
                this.phaser.loadTexture(null, this.frame);
                return;
            }
            if (this._texture === value) return;
            this._texture = value;

            // 如果frame不存在，则使用第一帧
            var frame = this.frame;
            if (!value.getFrame(this.frame)) frame = 0;

            // 载入图片(通过设置frame来起效)
            this.phaser.key = value.key;
            this.frame = frame;

            this._dispatchLayoutArgumentChanged('size');

            if (this._onTextureChanged) {
                this._onTextureChanged.dispatch();
            }
        }
    },

    /**
     *  获取or设置当前的图片帧，一般是图集才会用到该属性（可以为数字或别名）
     *  @property {int|string} frame
     */
    frame : {
        get: function () {
            if (!this.texture) return null;
            return this.phaser.frameName;
        },

        set: function (value) {
            if (!this.texture) return;
            this.phaser.loadTexture(this.texture.key, value);
            this.setWidth(this.width);
            this.setHeight(this.height);

            // 设置9宫格边距
            this._resetNinePadding();
            this._dispatchLayoutArgumentChanged('size');

            if (this._onTextureChanged) {
                this._onTextureChanged.dispatch();
            }
        }
    },

    /**
     * @property {qc.Rectangle} nativeSize - 图片实际大小
     * @readonly
     */
    nativeSize : {
        get : function() {
            return (this.phaser && this.phaser.texture && this.phaser.texture.crop) || new qc.Rectangle(0, 0, 0, 0);
        }
    },

    /**
     * 设置or获取图片显示的方式
     * @property {enum} imageType
     * @internal
     */
    imageType : {
        get: function() {
            return this._imageType;
        },
        set: function(value) {
            this._imageType = value;
        }
    },

    /**
     *  获取or设置图片的九宫格的上
     *  @property {int} borderTop
     *  @internal
     */
    borderTop : {
        get: function () {
            return this._borderTop;
        },

        set: function (value) {
            this._borderTop = value;
        }
    },
    /**
     *  获取or设置图片的九宫格的下
     *  @property {int} borderBottom
     *  @internal
     */
    borderBottom : {
        get: function () {
            return this._borderBottom;
        },

        set: function (value) {
            this._borderBottom = value;
        }
    },

    /**
     *  获取or设置图片的九宫格的左
     *  @property {int} borderLeft
     *  @internal
     */
    borderLeft : {
        get: function () {
            return this._borderLeft;
        },

        set: function (value) {
            this._borderLeft = value;
        }
    },

    /**
     *  获取or设置图片的九宫格的右
     *  @property {int} borderRight
     *  @internal
     */
    borderRight : {
        get: function () {
            return this._borderRight;
        },

        set: function (value) {
            this._borderRight = value;
        }
    },

    /**
     * @property {Phaser.Signal} onTextureChanged - 当显示的贴图发生变化时触发
     */
    onTextureChanged : {
        get : function() {
            if (!this._onTextureChanged) {
                this._onTextureChanged = new Phaser.Signal();
            }
            return this._onTextureChanged;
        }
    },

    /**
     * @property {string} class - 类的名字
     * @internal
     */
    class : {
        get : function() { return 'qc.UIImage'; }
    }
});

/**
 * 设置图片大小为实际大小
 */
UIImage.prototype.resetNativeSize = function() {
    if (this.parent) {
        this.width = this.nativeSize.width;
        this.height = this.nativeSize.height;
    }
    else {
        this.setWidth(this.nativeSize.width);
        this.setHeight(this.nativeSize.width);
    }
};

/**
 * 设置节点的宽度
 * @protected
 * @override
 */
UIImage.prototype.setWidth = function(w) {
    Node.prototype.setWidth.call(this, w);
    if (!this.phaser.texture.trim) {
        this.phaser.texture.frame.width = w;
    }
};

/**
 * 设置节点的高度
 * @protected
 * @override
 */
UIImage.prototype.setHeight = function(h) {
    Node.prototype.setHeight.call(this, h);
    if (!this.phaser.texture.trim) {
        this.phaser.texture.frame.height = h;
    }
};

/**
 * 设置9宫格图片的边距
 * @private
 */
UIImage.prototype._resetNinePadding = function() {
    var atlas = this.texture;
    if (!atlas) return;

    var padding = atlas.getPadding(this.frame);
    this._borderLeft = padding[0];
    this._borderTop = padding[1];
    this._borderRight = padding[2];
    this._borderBottom = padding[3];
};

/**
 * 获取需要被序列化的信息描述
 * @overide
 * @internal
 */
UIImage.prototype.getMeta = function() {
    var s = qc.Serializer;
    var json = qc.Node.prototype.getMeta.call(this);

    // 增加UIImage需要序列化的内容
    json.texture = s.TEXTURE;
    json.frame = s.AUTO;
    json.imageType = s.NUMBER;
    return json;
};

/**
 * hack image 的 _renderWebGL 方法为了绘制九宫格
 * @hackpp
 */
var imageRenderWebGL = function(renderSession)
{
    var _qc = this._qc;
    if (_qc.imageType === qc.UIImage.IMAGE_TYPE_SIMPLE) {
        // 类别是普通拉伸，或者Slice拉升却没有任何九宫格信息，使用基类的方法
        return Phaser.Sprite.prototype._renderWebGL.call(this, renderSession);
    }

    // if the sprite is not visible or the alpha is 0 then no need to render this element
    if (!this.visible || this.alpha <= 0 || !this.renderable) return;

    var i, j;
    if (this.softClip) {
        SoftClipManager.getManager(renderSession).pushPolygon(this.softClip);
    }
    // do a quick check to see if this element has a mask or a filter.
    if (this._graphicsFilter || this._mask || this._filters)
    {
        var spriteBatch =  renderSession.spriteBatch;

        if (this._graphicsFilter) {
            spriteBatch.flush();
            renderSession.filterManager.pushFilter(this._graphicsFilter);
        }

        // push filter first as we need to ensure the stencil buffer is correct for any masking
        if (this._filters && !this.filterSelf)
        {
            spriteBatch.flush();
            renderSession.filterManager.pushFilter(this._filterBlock);
        }

        if (this._mask)
        {
            spriteBatch.stop();
            renderSession.maskManager.pushMask(this.mask, renderSession);
            spriteBatch.start();
        }

        if (this._filters && this.filterSelf) {
            spriteBatch.flush();
            renderSession.filterManager.pushFilter(this._filterBlock);
        }

        // add this sprite to the batch
        // @hackpp  hack this line
        imageFillVBO(spriteBatch, this);

        if (this._filters && this.filterSelf) {
            spriteBatch.stop();
            renderSession.filterManager.popFilter();
            spriteBatch.start();
        }

        // now loop through the children and make sure they get rendered
        for (i = 0; i < this.children.length; i++)
        {
            this.children[i]._renderWebGL(renderSession);
        }

        // time to stop the sprite batch as either a mask element or a filter draw will happen next
        spriteBatch.stop();

        if (this._mask) renderSession.maskManager.popMask(this._mask, renderSession);
        if (this._filters && !this.filterSelf) renderSession.filterManager.popFilter();
        if (this._graphicsFilter) renderSession.filterManager.popFilter();
        spriteBatch.start();
    }
    else
    {
        // @hackpp  hack this line
        imageFillVBO(renderSession.spriteBatch, this);

        // simple render children!
        for (i = 0; i < this.children.length; i++)
        {
            this.children[i]._renderWebGL(renderSession);
        }
    }
    if (this.softClip) {
        SoftClipManager.getManager(renderSession).popPolygon();
    }
};

// 根据九宫格信息，生成拉伸的图元
var generateSlicedSprite = function(spriteBatch, sprite) {
    var texture = sprite.texture;
    var uvs = texture._uvs;
    if (! uvs) return;

    var _qc = sprite._qc;
    var bL = _qc.borderLeft;
    var bR = _qc.borderRight;
    var bT = _qc.borderTop;
    var bB = _qc.borderBottom;

    var aX = sprite.anchor.x;
    var aY = sprite.anchor.y;

    var w0, w1, h0, h1;

    if (texture.trim)
    {
        // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords..
        var trim = texture.trim;

        w1 = trim.x - aX * trim.width;
        w0 = w1 + texture.crop.width;

        h1 = trim.y - aY * trim.height;
        h0 = h1 + texture.crop.height;

    }
    else
    {
        w0 = (texture.frame.width ) * (1-aX);
        w1 = (texture.frame.width ) * -aX;

        h0 = texture.frame.height * (1-aY);
        h1 = texture.frame.height * -aY;
    }

    var tw = texture.baseTexture.width;
    var th = texture.baseTexture.height;
    var uvScractch = [
        new Phaser.Point(uvs.x0, uvs.y0),
        new Phaser.Point(uvs.x0 + bL / tw, uvs.y0 + bT / th),
        new Phaser.Point(uvs.x2 - bR / tw, uvs.y2 - bB / th),
        new Phaser.Point(uvs.x2, uvs.y2)
    ];

    // 调整 boarder
    var width = Math.abs(w0 - w1);
    var height = Math.abs(h0 - h1);
    if (width < bL + bR) {
        bL = width * bL / (bL + bR);
        bR = width - bL;
    }
    if (w1 > w0) { bL = -bL; bR = -bR; }
    if (height < bT + bB) {
        bT = height * bT / (bT + bB);
        bB = height - bT;
    }
    if (h1 > h0) { bT = -bT; bB = -bB; }

    var vertScratch = [
        new Phaser.Point(w1, h1),
        new Phaser.Point(w1 + bL, h1 + bT),
        new Phaser.Point(w0 - bR, h0 - bB),
        new Phaser.Point(w0, h0)
    ];

    var tint = sprite.tint;
    tint = (tint >> 16) + (tint & 0xff00) + ((tint & 0xff) << 16) +
    (sprite.worldAlpha * 255 << 24);

    var resolution = texture.baseTexture.resolution;
    var worldTransform = sprite.worldTransform;

    var a = worldTransform.a / resolution;
    var b = worldTransform.b / resolution;
    var c = worldTransform.c / resolution;
    var d = worldTransform.d / resolution;
    var tx = worldTransform.tx;
    var ty = worldTransform.ty;

    for (var x = 0; x < 3; ++x) {
        var x2 = x + 1;

        for (var y = 0; y < 3; ++y) {
            var y2 = y + 1;

            webGLAddQuad(spriteBatch, sprite,
                vertScratch[x].x, vertScratch[y].y,
                vertScratch[x2].x, vertScratch[y2].y,
                uvScractch[x].x, uvScractch[y].y,
                uvScractch[x2].x, uvScractch[y2].y,
                a, b, c, d, tx, ty,
                tint);
        }
    }
};

// 增加定点
var webGLAddQuad = function(spriteBatch, sprite, w1, h1, w0, h0, uvx0, uvy0, uvx1, uvy1, a, b, c, d, tx, ty, tint) {
    var clipMgr = spriteBatch.renderSession.softClipManager;
    if (clipMgr && clipMgr.needClip) {
        // 需要进行软件裁切
        clipMgr.renderSprite(spriteBatch.renderSession, sprite,
            w1, h1, w0, h0,
            uvx0, uvy0, uvx1, uvy1,
            a, b, c, d, tx, ty,
            tint);
        return;
    }

    if(spriteBatch.currentBatchSize >= spriteBatch.size)
    {
        spriteBatch.flush();
        spriteBatch.currentBaseTexture = sprite.texture.baseTexture;
    }

    var colors = spriteBatch.colors;
    var positions = spriteBatch.positions;
    var index = spriteBatch.currentBatchSize * 4 * spriteBatch.vertSize;

    if(spriteBatch.renderSession.roundPixels)
    {
        // xy
        positions[index] = a * w1 + c * h1 + tx | 0;
        positions[index+1] = d * h1 + b * w1 + ty | 0;

        // xy
        positions[index+5] = a * w0 + c * h1 + tx | 0;
        positions[index+6] = d * h1 + b * w0 + ty | 0;

        // xy
        positions[index+10] = a * w0 + c * h0 + tx | 0;
        positions[index+11] = d * h0 + b * w0 + ty | 0;

        // xy
        positions[index+15] = a * w1 + c * h0 + tx | 0;
        positions[index+16] = d * h0 + b * w1 + ty | 0;
    }
    else
    {
        // xy
        positions[index] = a * w1 + c * h1 + tx;
        positions[index+1] = d * h1 + b * w1 + ty;

        // xy
        positions[index+5] = a * w0 + c * h1 + tx;
        positions[index+6] = d * h1 + b * w0 + ty;

        // xy
        positions[index+10] = a * w0 + c * h0 + tx;
        positions[index+11] = d * h0 + b * w0 + ty;

        // xy
        positions[index+15] = a * w1 + c * h0 + tx;
        positions[index+16] = d * h0 + b * w1 + ty;
    }

    // uv
    positions[index+2] = uvx0;
    positions[index+3] = uvy0;

    // uv
    positions[index+7] = uvx1;
    positions[index+8] = uvy0;

    // uv
    positions[index+12] = uvx1;
    positions[index+13] = uvy1;

    // uv
    positions[index+17] = uvx0;
    positions[index+18] = uvy1;

    // color and alpha
    colors[index+4] = colors[index+9] = colors[index+14] = colors[index+19] = tint;

    // increment the batchsize
    spriteBatch.sprites[spriteBatch.currentBatchSize++] = sprite;

};

var generateTiledSprite = function(spriteBatch, sprite) {
    var texture = sprite.texture;
    var uvs = texture._uvs;
    if (! uvs) return;

    var _qc = sprite._qc;
    var bL = _qc.borderLeft;
    var bR = _qc.borderRight;
    var bT = _qc.borderTop;
    var bB = _qc.borderBottom;

    var aX = sprite.anchor.x;
    var aY = sprite.anchor.y;

    var w0, w1, h0, h1;

    if (texture.trim)
    {
        // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords..
        var trim = texture.trim;

        w1 = trim.x - aX * trim.width;
        w0 = w1 + texture.crop.width;

        h1 = trim.y - aY * trim.height;
        h0 = h1 + texture.crop.height;

    }
    else
    {
        w0 = (texture.frame.width ) * (1-aX);
        w1 = (texture.frame.width ) * -aX;

        h0 = texture.frame.height * (1-aY);
        h1 = texture.frame.height * -aY;
    }

    var tw = texture.baseTexture.width;
    var th = texture.baseTexture.height;
    var uvScractch = [
        new Phaser.Point(uvs.x0, uvs.y0),
        new Phaser.Point(uvs.x0 + bL / tw, uvs.y0 + bT / th),
        new Phaser.Point(uvs.x2 - bR / tw, uvs.y2 - bB / th),
        new Phaser.Point(uvs.x2, uvs.y2)
    ];

    var uvMinX = uvs.x0 + bL / tw;
    var uvMinY = uvs.y0 + bT / th;
    var uvMaxX = uvs.x2 - bR / tw;
    var uvMaxY = uvs.y2 - bB / th;

    // 调整 boarder
    var width = Math.abs(w0 - w1);
    var height = Math.abs(h0 - h1);
    if (width < bL + bR) {
        bL = width * bL / (bL + bR);
        bR = width - bL;
    }
    if (w1 > w0) { bL = -bL; bR = -bR; }
    if (height < bT + bB) {
        bT = height * bT / (bT + bB);
        bB = height - bT;
    }
    if (h1 > h0) { bT = -bT; bB = -bB; }

    // 计算步进
    var xMax = w0 - bR;
    var yMax = h0 - bB;
    var xMin = w1 + bL;
    var yMin = h1 + bT;

    var vertScratch = [
        new Phaser.Point(w1, h1),
        new Phaser.Point(xMin, yMin),
        new Phaser.Point(xMax, yMax),
        new Phaser.Point(w0, h0)
    ];

    var tint = sprite.tint;
    tint = (tint >> 16) + (tint & 0xff00) + ((tint & 0xff) << 16) +
    (sprite.worldAlpha * 255 << 24);

    var resolution = texture.baseTexture.resolution;
    var worldTransform = sprite.worldTransform;

    var a = worldTransform.a / resolution;
    var b = worldTransform.b / resolution;
    var c = worldTransform.c / resolution;
    var d = worldTransform.d / resolution;
    var tx = worldTransform.tx;
    var ty = worldTransform.ty;

    var tileWidth = (uvs.x2 - uvs.x0) * tw - bL - bR;
    var tileHeight = (uvs.y2 - uvs.y0) * th - bT - bB;
    if (width - bL - bR > tileWidth * 32)
        tileWidth = (width - bL - bR) / 32;

    if (height - bT - bB > tileHeight * 32)
        tileHeight = (height - bT - bB) / 32;

    // 中部填充
    var clippedX = uvMaxX;
    var clippedY = uvMaxY;

    if (tileWidth > 0 && tileHeight > 0) {
        for (var y1 = yMin; y1 < yMax; y1 += tileHeight) {
            var y2 = y1 + tileHeight;
            if (y2 > yMax) {
                clippedY = uvMinY + (uvMaxY - uvMinY) * (yMax - y1) / (y2 - y1);
                y2 = yMax;
            }

            clippedX = uvMaxX;
            for (var x1 = xMin; x1 < xMax; x1 += tileWidth) {
                var x2 = x1 + tileWidth;
                if (x2 > xMax)  {
                    clippedX = uvMinX + (uvMaxX - uvMinX) * (xMax - x1) / (x2 - x1);
                    x2 = xMax;
                }

                webGLAddQuad(spriteBatch, sprite,
                    x1, y1,
                    x2, y2,
                    uvMinX, uvMinY,
                    clippedX, clippedY,
                    a, b, c, d, tx, ty,
                    tint);
            }
        }
    }

    // 左右填充
    clippedX = uvMaxX;
    clippedY = uvMaxY;
    if (tileHeight) {
        for (var y1 = yMin; y1 < yMax; y1 += tileHeight) {
            var y2 = y1 + tileHeight;
            if (y2 > yMax) {
                clippedY = uvMinY + (uvMaxY - uvMinY) * (yMax - y1) / (y2 - y1);
                y2 = yMax;
            }

            webGLAddQuad(spriteBatch, sprite,
                w1, y1,
                xMin, y2,
                uvs.x0, uvMinY,
                uvMinX, clippedY,
                a, b, c, d, tx, ty,
                tint);
            webGLAddQuad(spriteBatch, sprite,
                xMax, y1,
                w0, y2,
                uvMaxX, uvMinY,
                uvs.x2, clippedY,
                a, b, c, d, tx, ty,
                tint);
        }
    }

    // 上下填充
    clippedX = uvMaxX;
    clippedY = uvMaxY;
    if (tileWidth) {
        for (var x1 = xMin; x1 < xMax; x1 += tileWidth) {
            var x2 = x1 + tileWidth;
            if (x2 > xMax) {
                clippedX = uvMinX + (uvMaxX - uvMinX) * (xMax - x1) / (x2 - x1);
                x2 = xMax;
            }

            webGLAddQuad(spriteBatch, sprite,
                x1, h1,
                x2, yMin,
                uvMinX, uvs.y0,
                clippedX, uvMinY,
                a, b, c, d, tx, ty,
                tint);
            webGLAddQuad(spriteBatch, sprite,
                x1, yMax,
                x2, h0,
                uvMinX, uvMaxY,
                clippedX, uvs.y2,
                a, b, c, d, tx, ty,
                tint);
        }
    }

    // 四角填充
    webGLAddQuad(spriteBatch, sprite,
        vertScratch[0].x, vertScratch[0].y,
        vertScratch[1].x, vertScratch[1].y,
        uvScractch[0].x, uvScractch[0].y,
        uvScractch[1].x, uvScractch[1].y,
        a, b, c, d, tx, ty,
        tint);
    webGLAddQuad(spriteBatch, sprite,
        vertScratch[2].x, vertScratch[0].y,
        vertScratch[3].x, vertScratch[1].y,
        uvScractch[2].x, uvScractch[0].y,
        uvScractch[3].x, uvScractch[1].y,
        a, b, c, d, tx, ty,
        tint);
    webGLAddQuad(spriteBatch, sprite,
        vertScratch[2].x, vertScratch[2].y,
        vertScratch[3].x, vertScratch[3].y,
        uvScractch[2].x, uvScractch[2].y,
        uvScractch[3].x, uvScractch[3].y,
        a, b, c, d, tx, ty,
        tint);
    webGLAddQuad(spriteBatch, sprite,
        vertScratch[0].x, vertScratch[2].y,
        vertScratch[1].x, vertScratch[3].y,
        uvScractch[0].x, uvScractch[2].y,
        uvScractch[1].x, uvScractch[3].y,
        a, b, c, d, tx, ty,
        tint);
};

/**
 * hack image 的 _renderCanvas 方法为了绘制九宫格
 * @hackpp
 */
var imageRenderCanvas = function(renderSession) {
    // If the sprite is not visible or the alpha is 0 then no need to render this element
    if (this.visible === false || this.alpha === 0 || this.renderable === false || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) return;

    var textureValid = this.texture.valid;
    var _qc = this._qc;
    var imageType = _qc.imageType;

    if (this.maskPixel && textureValid && imageType === qc.UIImage.IMAGE_TYPE_SIMPLE) {
        var bufferPool = renderSession.bufferPool || ( renderSession.bufferPool = []);
        var oldContext = renderSession.context;
        var oldOffX = (oldContext.globalOffX || 0);
        var oldOffY = (oldContext.globalOffY || 0);

        var filterArea = this.maskPixel.target.filterArea || this.maskPixel.target.getBounds();
        var minX = Math.max(oldOffX, filterArea.x);
        var minY = Math.max(oldOffY, filterArea.y);
        var maxX = Math.min(filterArea.x + filterArea.width, oldContext.canvas.width + oldOffX);
        var maxY = Math.min(filterArea.y + filterArea.height, oldContext.canvas.height + oldOffY);
        filterArea.x = minX;
        filterArea.y = minY;
        filterArea.width = maxX - minX;
        filterArea.height = maxY - minY;

        var canvasBuffer =  bufferPool.pop();
        if (!canvasBuffer) {
            canvasBuffer = new PIXI.CanvasBuffer(renderSession.context.canvas.width * renderSession.resolution, renderSession.context.canvas.height * renderSession.resolution);
            canvasBuffer.context._setTransform = canvasBuffer.context.setTransform;
            canvasBuffer.context.setTransform = function(a, b, c, d, tx, ty) {
                this._setTransform(a, b, c, d, tx - (this.globalOffX || 0), ty - (this.globalOffY || 0));
            };
        }
        canvasBuffer.resize(filterArea.width * renderSession.resolution, filterArea.height * renderSession.resolution);
        canvasBuffer.context.clearRect(0, 0, filterArea.width * renderSession.resolution, filterArea.height * renderSession.resolution);
        canvasBuffer.context.globalOffX = filterArea.x * renderSession.resolution + oldOffX;
        canvasBuffer.context.globalOffY = filterArea.y * renderSession.resolution + oldOffY;
        renderSession.context = canvasBuffer.context;
    }

    var context = renderSession.context;

    if (this.blendMode !== renderSession.currentBlendMode)
    {
        renderSession.currentBlendMode = this.blendMode;
        context.globalCompositeOperation = PIXI.blendModesCanvas[renderSession.currentBlendMode];
    }

    if (this._mask)
    {
        renderSession.maskManager.pushMask(this._mask, renderSession);
    }

    //  Ignore null sources
    if (textureValid)
    {
        var resolution = this.texture.baseTexture.resolution / renderSession.resolution;

        context.globalAlpha = this.worldAlpha;

        //  If smoothingEnabled is supported and we need to change the smoothing property for this texture
        ////---- Hackpp here resize 时 context 中的平滑属性会被变更，需要重新设置
        if (renderSession.smoothProperty &&
            (renderSession.scaleMode !== this.texture.baseTexture.scaleMode ||
            context[renderSession.smoothProperty] !== (renderSession.scaleMode === PIXI.scaleModes.LINEAR)))
        {
            renderSession.scaleMode = this.texture.baseTexture.scaleMode;
            context[renderSession.smoothProperty] = (renderSession.scaleMode === PIXI.scaleModes.LINEAR);
        }

        //  If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions
        var dx = (this.texture.trim) ? this.texture.trim.x - this.anchor.x * this.texture.trim.width : this.anchor.x * -this.texture.frame.width;
        var dy = (this.texture.trim) ? this.texture.trim.y - this.anchor.y * this.texture.trim.height : this.anchor.y * -this.texture.frame.height;
        var width = (this.texture.trim) ? this.texture.crop.width : this.texture.frame.width;
        var height = (this.texture.trim) ? this.texture.crop.height : this.texture.frame.height;

        //  Allow for pixel rounding
        if (renderSession.roundPixels)
        {
            context.setTransform(
                this.worldTransform.a,
                this.worldTransform.b,
                this.worldTransform.c,
                this.worldTransform.d,
                (this.worldTransform.tx * renderSession.resolution) | 0,
                (this.worldTransform.ty * renderSession.resolution) | 0);
            dx = dx | 0;
            dy = dy | 0;
        }
        else
        {
            context.setTransform(
                this.worldTransform.a,
                this.worldTransform.b,
                this.worldTransform.c,
                this.worldTransform.d,
                this.worldTransform.tx * renderSession.resolution,
                this.worldTransform.ty * renderSession.resolution);
        }

        var canvasDrawImage = function(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) {
            context.drawImage(image, sx, sy, sWidth || 1, sHeight || 1, dx, dy, dWidth || 1, dHeight || 1);
        };

        var texture;
        var uvx, uvy, uvw, uvh;

        if (this.tint !== 0xFFFFFF)
        {
            if (this.cachedTint !== this.tint)
            {
                this.cachedTint = this.tint;
                this.tintedTexture = PIXI.CanvasTinter.getTintedTexture(this, this.tint);
            }

            texture = this.tintedTexture;
            uvx = 0;
            uvy = 0;
            uvw = this.texture.crop.width;
            uvh = this.texture.crop.height;
        }
        else
        {
            texture = this.texture.baseTexture.source;
            uvx = this.texture.crop.x;
            uvy = this.texture.crop.y;
            uvw = this.texture.crop.width;
            uvh = this.texture.crop.height;
        }

        dx = Math.round(dx / resolution);
        dy = Math.round(dy / resolution);
        width = Math.round(width / resolution);
        height = Math.round(height / resolution);

        if (imageType === qc.UIImage.IMAGE_TYPE_SIMPLE) {

            // 普通类型，或者是 slice 但是没有任何九宫格信息
            if (!this.maskPixel) {
                canvasDrawImage(context, texture,
                    uvx, uvy, uvw, uvh,
                    dx, dy, width, height);
            }
        }
        else if (_qc.texture && width && height) {
            // 用于查找 canvas 的关键字
            var canvasKey = _qc.texture.uuid + imageType + _qc.frame + this.tint + width + height;
            var canvasFetchRet = qc.CanvasPool.get(canvasKey);
            var cacheCanvas = canvasFetchRet.canvas;
            if (canvasFetchRet.dirty) {
                var cacheContext = cacheCanvas.getContext('2d');

                // 初始化 canvas
                if (renderSession.smoothProperty &&
                    cacheContext[renderSession.smoothProperty] !== (renderSession.scaleMode === PIXI.scaleModes.LINEAR))
                    cacheContext[renderSession.smoothProperty] = (renderSession.scaleMode === PIXI.scaleModes.LINEAR);
                cacheCanvas.width = width;
                cacheCanvas.height = height;
                cacheContext.clearRect(0, 0, width, height);

                var bL = _qc.borderLeft;
                var bR = _qc.borderRight;
                var bT = _qc.borderTop;
                var bB = _qc.borderBottom;

                var uvScractch = [
                    new Phaser.Point(uvx, uvy),
                    new Phaser.Point(uvx + bL, uvy + bT),
                    new Phaser.Point(uvx + uvw - bR, uvy + uvh - bB),
                    new Phaser.Point(uvx + uvw, uvy + uvh)
                ];
                bL /= resolution;
                bT /= resolution;
                bR /= resolution;
                bB /= resolution;

                if (width < bL + bR) {
                    bL = width * bL / (bL + bR);
                    bR = width - bL;
                }
                if (height < bT + bB) {
                    bT = height * bT / (bT + bB);
                    bB = height - bT;
                }

                var vertScratch = [
                    new Phaser.Point(0, 0),
                    new Phaser.Point(bL, bT),
                    new Phaser.Point(width - bR, height - bB),
                    new Phaser.Point(width, height)
                ];

                if (imageType == qc.UIImage.IMAGE_TYPE_SLICED) {
                    for (var x = 0; x < 3; ++x) {
                        var x2 = x + 1;

                        for (var y = 0; y < 3; ++y) {
                            var y2 = y + 1;

                            var uvx0 = uvScractch[x].x;
                            var uvy0 = uvScractch[y].y;
                            var uvx1 = uvScractch[x2].x;
                            var uvy1 = uvScractch[y2].y;
                            var dx0 = vertScratch[x].x;
                            var dy0 = vertScratch[y].y;
                            var dx1 = vertScratch[x2].x;
                            var dy1 = vertScratch[y2].y;

                            if (uvx1 - uvx0 <= 0 ||
                                uvy1 - uvy0 <= 0 ||
                                dx1 - dx0 <= 0 ||
                                dy1 - dy0 <= 0) {
                                continue;
                            }

                            canvasDrawImage(cacheContext, texture,
                                uvx0, uvy0, uvx1 - uvx0, uvy1 - uvy0,
                                dx0, dy0, dx1 - dx0, dy1 - dy0);
                        }
                    }
                }
                else {
                    var uvMinX = uvScractch[1].x;
                    var uvMinY = uvScractch[1].y;
                    var uvMaxX = uvScractch[2].x;
                    var uvMaxY = uvScractch[2].y;

                    // 计算步进
                    var xMax = width - bR;
                    var yMax = height - bB;
                    var xMin = bL;
                    var yMin = bT;

                    var tileWidth = uvw / resolution - bL - bR;
                    var tileHeight = uvh / resolution - bT - bB;
                    if (width - bL - bR > tileWidth * 32)
                        tileWidth = (width - bL - bR) / 32;

                    if (height - bT - bB > tileHeight * 32)
                        tileHeight = (height - bT - bB) / 32;

                    var clippedX = uvMaxX;
                    var clippedY = uvMaxY;

                    if (tileWidth > 0 && tileHeight > 0) {
                        for (var y1 = yMin; y1 < yMax; y1 += tileHeight) {
                            var y2 = y1 + tileHeight;
                            if (y2 > yMax) {
                                clippedY = uvMinY + (uvMaxY - uvMinY) * (yMax - y1) / (y2 - y1);
                                y2 = yMax;
                            }

                            clippedX = uvMaxX;
                            for (var x1 = xMin; x1 < xMax; x1 += tileWidth) {
                                var x2 = x1 + tileWidth;
                                if (x2 > xMax) {
                                    clippedX = uvMinX + (uvMaxX - uvMinX) * (xMax - x1) / (x2 - x1);
                                    x2 = xMax;
                                }

                                if (clippedX - uvMinX <= 0 ||
                                    clippedY - uvMinY <= 0 ||
                                    x2 - x1 <= 0 ||
                                    y2 - y1 <= 0) {
                                    continue;
                                }

                                canvasDrawImage(cacheContext, texture,
                                    uvMinX, uvMinY, clippedX - uvMinX, clippedY - uvMinY,
                                    x1, y1,
                                    x2 - x1, y2 - y1);
                            }
                        }
                    }

                    // 左右填充
                    clippedY = uvMaxY;
                    if (tileHeight) {
                        for (var y1 = yMin; y1 < yMax; y1 += tileHeight) {
                            var y2 = y1 + tileHeight;
                            if (y2 > yMax) {
                                clippedY = uvMinY + (uvMaxY - uvMinY) * (yMax - y1) / (y2 - y1);
                                y2 = yMax;
                            }

                            canvasDrawImage(cacheContext, texture,
                                uvx, uvMinY, uvMinX - uvx, clippedY - uvMinY,
                                0, y1,
                                bL, y2 - y1);
                            canvasDrawImage(cacheContext, texture,
                                uvMaxX, uvMinY, uvx + uvw - uvMaxX, clippedY - uvMinY,
                                xMax, y1,
                                bR, y2 - y1);
                        }
                    }

                    // 上下填充
                    clippedX = uvMaxX;
                    if (tileWidth) {
                        for (var x1 = xMin; x1 < xMax; x1 += tileWidth) {
                            var x2 = x1 + tileWidth;
                            if (x2 > xMax) {
                                clippedX = uvMinX + (uvMaxX - uvMinX) * (xMax - x1) / (x2 - x1);
                                x2 = xMax;
                            }

                            canvasDrawImage(cacheContext, texture,
                                uvMinX, uvy, clippedX - uvMinX, uvMinY - uvy,
                                x1, 0,
                                x2 - x1, bT);
                            canvasDrawImage(cacheContext, texture,
                                uvMinX, uvMaxY, clippedX - uvMinX, uvy + uvh - uvMaxY,
                                x1, yMax,
                                x2 - x1, bB);
                        }
                    }

                    // 四个脚
                    canvasDrawImage(cacheContext, texture,
                        uvScractch[0].x, uvScractch[0].y,
                        uvScractch[1].x - uvScractch[0].x, uvScractch[1].y - uvScractch[0].y,
                        vertScratch[0].x, vertScratch[0].y,
                        vertScratch[1].x - vertScratch[0].x, vertScratch[1].y - vertScratch[0].y);
                    canvasDrawImage(cacheContext, texture,
                        uvScractch[2].x, uvScractch[0].y,
                        uvScractch[3].x - uvScractch[2].x, uvScractch[1].y - uvScractch[0].y,
                        vertScratch[2].x, vertScratch[0].y,
                        vertScratch[3].x - vertScratch[2].x, vertScratch[1].y - vertScratch[0].y);
                    canvasDrawImage(cacheContext, texture,
                        uvScractch[2].x, uvScractch[2].y,
                        uvScractch[3].x - uvScractch[2].x, uvScractch[3].y - uvScractch[2].y,
                        vertScratch[2].x, vertScratch[2].y,
                        vertScratch[3].x - vertScratch[2].x, vertScratch[3].y - vertScratch[2].y);
                    canvasDrawImage(cacheContext, texture,
                        uvScractch[0].x, uvScractch[2].y,
                        uvScractch[1].x - uvScractch[0].x, uvScractch[3].y - uvScractch[2].y,
                        vertScratch[0].x, vertScratch[2].y,
                        vertScratch[1].x - vertScratch[0].x, vertScratch[3].y - vertScratch[2].y);
                }
            }

            // 将缓存的 canvas 绘制到屏幕中
            canvasDrawImage(context, cacheCanvas,
                0, 0, width, height,
                dx, dy, width, height);
        }
    }

    // OVERWRITE
    for (var i = 0; i < this.children.length; i++)
    {
        this.children[i]._renderCanvas(renderSession);
    }

    if (this._mask)
    {
        renderSession.maskManager.popMask(renderSession);
    }

    if (this.maskPixel && textureValid && imageType === qc.UIImage.IMAGE_TYPE_SIMPLE) {
        context.globalCompositeOperation = 'destination-in';
        //  Allow for pixel rounding
        if (renderSession.roundPixels)
        {
            context.setTransform(
                this.worldTransform.a,
                this.worldTransform.b,
                this.worldTransform.c,
                this.worldTransform.d,
                (this.worldTransform.tx * renderSession.resolution) | 0,
                (this.worldTransform.ty * renderSession.resolution) | 0);
            dx = dx | 0;
            dy = dy | 0;
        }
        else
        {
            context.setTransform(
                this.worldTransform.a,
                this.worldTransform.b,
                this.worldTransform.c,
                this.worldTransform.d,
                this.worldTransform.tx * renderSession.resolution,
                this.worldTransform.ty * renderSession.resolution);
        }
        canvasDrawImage(context, texture,
            uvx, uvy, uvw, uvh,
            dx, dy, width, height);

        context.globalCompositeOperation = 'source-over';

        renderSession.context = oldContext;
        if (renderSession.roundPixels)
        {
            renderSession.context.setTransform(
                1, 0, 0, 1,
                filterArea.x * renderSession.resolution | 0,
                filterArea.y * renderSession.resolution | 0);
        }
        else
        {
            renderSession.context.setTransform(
                1, 0, 0, 1,
                filterArea.x * renderSession.resolution,
                filterArea.y * renderSession.resolution);
        }
        renderSession.context.drawImage(canvasBuffer.canvas,
            0, 0, canvasBuffer.canvas.width, canvasBuffer.canvas.height,
            0, 0, canvasBuffer.canvas.width, canvasBuffer.canvas.height);
        bufferPool.push(canvasBuffer);
    }
};

// 填充 UIIamge 的 VBO，使用九宫格信息
var imageFillVBO = function(spriteBatch, sprite) {
    switch (sprite._qc.imageType)
    {
    case UIImage.IMAGE_TYPE_SLICED :
        generateSlicedSprite(spriteBatch, sprite);
        break;

    case UIImage.IMAGE_TYPE_TILED :
        generateTiledSprite(spriteBatch, sprite);
        break;
    }
};

// hack uiimage 的 updateTransform，用于支持 skewX, skewY
// 注意，skewX、skewY 是给 dragon bones 用的，且不是标准的 skew
// 对于 spine 骨骼来说只用到 rotation 而不用 skew 这东西
//@hackpp
var imageUpdateTransform = function()
{
    if (! this._qc.skewX && ! this._qc.skewY) {
        // 没有 skew 信息，使用基类的方法
        return Phaser.Sprite.prototype.updateTransform.call(this);
    }

    if (!this.parent)
    {
        return;
    }

    if(!this.visible)return;

    // 是否更新 sin cos 信息
    if (this.rotation !== this.rotationCache) {
        this.rotationCache=this.rotation;
        this._sr=Math.sin(this.rotation);
        this._cr=Math.cos(this.rotation);
    }

    // 世界矩阵
    var a, b, c, d, tx, ty;
    var skewa, skewb, skewc, skewd, skewtx, skewty;
    var rota, rotb, rotc, rotd, rottx, rotty;
    var pta, ptb, ptc, ptd;

    // skew 矩阵
    skewa  = Math.cos(this._qc.skewY || 0);
    skewb  = Math.sin(this._qc.skewY || 0);
    skewc  = -Math.sin(this._qc.skewX || 0);
    skewd  = Math.cos(this._qc.skewX || 0);
    skewtx = this.position.x;
    skewty = this.position.y;

    // rotate 矩阵
    rota  = this._cr * this.scale.x;
    rotb  = this._sr * this.scale.x;
    rotc  = -this._sr * this.scale.y;
    rotd  = this._cr * this.scale.y;
    rottx = 0;
    rotty = 0;

    // skew * rotate
    a  = rota * skewa + rotb * skewc;
    b  = rota * skewb + rotb * skewd;
    c  = rotc * skewa + rotd * skewc;
    d  = rotc * skewb + rotd * skewd;
    tx = rottx * skewa + rotty * skewc + skewtx;
    ty = rottx * skewb + rotty * skewd + skewty;

    // 世界 * (skew * rotate) * [x, y, 1]
    var wt = this.worldTransform;
    var pt = this.parent.worldTransform;
    pta = pt.a;
    ptb = pt.b;
    ptc = pt.c;
    ptd = pt.d;
    wt.a  = a * pta + b * ptc;
    wt.b  = a * ptb + b * ptd;
    wt.c  = c * pta + d * ptc;
    wt.d  = c * ptb + d * ptd;
    wt.tx = tx * pta + ty * ptc + pt.tx;
    wt.ty = tx * ptb + ty * ptd + pt.ty;

    // multiply the alphas..
    this.worldAlpha = this.alpha * this.parent.worldAlpha;

    //  Custom callback?
    if (this.transformCallback)
    {
        this.transformCallback.call(this.transformCallbackContext, wt, pt);
    }

    if(this._cacheAsBitmap)return;

    for(var i=0,j=this.children.length; i<j; i++)
    {
        this.children[i].updateTransform();
    }
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 挂载UI界面的根节点，所有的UI元素都应该在此节点下面（通过UI编辑器保证）
 * 此节点与相机的位置保持一致
 *
 * @class qc.UIRoot
 * @extends qc.Node
 * @param {Phaser.Game} game - A reference to the currently running game.
 * @constructor
 * @internal
 */
var UIRoot = qc.UIRoot = function(game, uuid) {
    // 先调用基类的初始化
    qc.Node.call(this, new Phaser.Group(game.phaser, null), null, uuid);

    // 设置节点的名字
    this.name = "UIRoot";

    // 挂载缩放的适配器
    var restore = uuid !== undefined;
    if (! restore) {
        var s = this.addScript('qc.ScaleAdapter');
        s.referenceResolution = new qc.Point(640, 960);
        s.manualType = qc.ScaleAdapter.EXPAND;
        s.fullTarget = true;
    }

    this.setAnchor(new qc.Point(0, 0), new qc.Point(0, 0));
    this.pivotX = 0;
    this.pivotY = 0;
    var worldScale = this.getWorldScale();
    this.setTransformToWorld(0, 0, worldScale.x, worldScale.y,
        this.getWorldRotation());
};
UIRoot.prototype = Object.create(qc.Node.prototype);
UIRoot.prototype.constructor = UIRoot;

Object.defineProperties(UIRoot.prototype, {
    /**
     * @property {string} class - 类名字
     * @readonly
     * @internal
     */
    class : {
        get : function() { return 'qc.UIRoot' }
    }
});

/**
 * The core postUpdate - as called by World.
 * @method qc.UIRoot#postUpdate
 * @protected
 */
UIRoot.prototype.postUpdate = function() {
    // 目前我们确保世界的大小和屏幕的大小一致(缩放也一致)，并且相机保持不动，因此只需要保证其位置永远为0即可
    this.x = 0;
    this.y = 0;
};

/**
 * @author wudm
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * sprite 精灵对象，包含动画，一般用于非 UI 的所有图片元素
 *
 * @class qc.Sprite
 * @extends qc.Node
 * @param {qc.Game} game
 * @constructor
 * @internal
 */
var Sprite = qc.Sprite = function(game, parent, uuid) {
    var phaserImage = new Phaser.Sprite(game.phaser);
    qc.Node.call(this, phaserImage, parent, uuid);

    // 设置默认的名字
    this.name = 'Sprite';

    // 设置默认的动作类型
    this.animationType = qc.Sprite.NONE_ANIMATION;

    // 我们需要重载掉 Phaser 的 setFrame 来支持帧动画缩放
    this._phaserImageSetFrame = phaserImage.setFrame;
    phaserImage.setFrame = setFrame;

    // 重载掉绘制，让骨骼动画的根节点不显示
    phaserImage._renderWebGL = spriteRenderWebGL;
    phaserImage._renderCanvas = spriteRenderCanvas;
};
Sprite.prototype = Object.create(qc.Node.prototype);
Sprite.prototype.constructor = Sprite;

/**
 * 动作类型：无动作
 * @constant
 * @type {number}
 */
Sprite.NONE_ANIMATION = 0;

/**
 * 动作类型：帧动画
 * @constant
 * @type {number}
 */
Sprite.FRAME_ANIMATION = 1;

/**
 * 动作类型：DRAGON_BONES
 * @constant
 * @type {number}
 */
Sprite.DRAGON_BONES = 2;

/**
 * 动作类型：帧采样动作
 * @constant
 * @type {number}
 */
Sprite.FRAME_SAMPLES = 3;

// 帧采样动画播放的时候插值倍数
var sInterpolationCo = 8;

/**
 * 播放图片的动画
 * @property {string} animationName
 * @property {speed} 播放速度 例如 2 表示 2 倍速播放
 * @property {loop} 是否循环播放
 */
Sprite.prototype.playAnimation = function(animationName, speed, loop) {
    var self = this;
    var animationType = self.animationType;
    if (animationType === Sprite.NONE_ANIMATION || !animationName) return;

    if (animationType === Sprite.DRAGON_BONES) {
        var animation = self._armature.animation;

        if (animation.animationNameList.indexOf(animationName) < 0) {
            console.warn("没有动作名为", animationName, "的动作");
            return;
        }

        var playDuration = undefined;
        if (typeof(speed) === 'number' && speed != 1 && speed > 0) {
            // 获取动作时长
            var i = animation.animationNameList.length;
            var animationData;
            while (i--) {
                if (animation._animationDataList[i].name == animationName) {
                    animationData = animation._animationDataList[i];
                    break;
                }
            }
            if (animationData) {  playDuration = animationData.duration / speed;  }
        }

        var loopTimes;

        if (loop === true) {
            // 确定循环动作
            loopTimes = 2097152; /* (2 << 20) */
        }
        else if (loop === false) {
            // 确定单次播放
            loopTimes = 1;
        }
        else {
            // 使用预设的播放次数
            loopTimes = undefined;
        }

        // 删除 pause 状态
        self.paused = false;
        self._armature.animation.gotoAndPlay(animationName, undefined, playDuration, loopTimes);
        return;
    }

    // 默认为普通帧动画、采样动画
    var animationData = this._animation[animationName];
    if (!animationData) {
        self.game.log.error('Animation {0} not exists', animationName);
        return;
    }

    // 注册动作给 phaser
    var rate = animationData["rate"] ? animationData["rate"] : 60;
    if (animationType === Sprite.FRAME_SAMPLES)
        rate = rate * sInterpolationCo;

    if (!self.phaser.animations._anims[animationName]) {
        // 构造动作
        self.phaser.animations.add(animationName, animationData["frames"], rate);

        // 关注动作事件
        var animation = self.phaser.animations._anims[animationName];
        animation.onStart.add(function() {
            if (self._onStart) self._onStart.dispatch(self.lastAnimationName);
        });
        animation.onComplete.add(function() {
            if (self._onFinished) self._onFinished.dispatch(self.lastAnimationName);
        });
        animation.onLoop.add(function() {
            if (self._onLoopFinished) self._onLoopFinished.dispatch(self.lastAnimationName);
        });
    }

    if (typeof(loop) !== 'boolean') {
        // 参数没有指定 loop 行为，则使用配置的 loop
        loop = !!animationData['loop'];
    }

    this._slowMotion = this.game.time.timeScale;
    this._speed = (speed ? speed : 1.0) * rate;

    // 开始播放这组动作
    self.phaser.animations.play(animationName, this._speed / this._slowMotion, loop);
};

/**
 * 停止动画的播放
 */
Sprite.prototype.stop = function() {
    var self = this;
    var animationType = self.animationType;

    if (animationType === Sprite.NONE_ANIMATION) return;

    // 动画非播放中，不需要处理
    if (!self.isPlaying) return;

    if (animationType === Sprite.DRAGON_BONES) {
        var animation = self._armature.animation;
        animation.stop();
        return;
    }

    // 默认为普通帧动画、采样动画
    var currentAnim = self.phaser.animations.currentAnim;
    if (currentAnim) currentAnim.stop(undefined, true);
};

/**
 * 负责驱动精灵的骨骼动画
 */
Sprite.prototype.update = function() {
    if (this.animationType === qc.Sprite.DRAGON_BONES &&
        this._armature) {

        // 当前动作静止
        if (this.isPaused()) return;

        // 骨骼动画需要驱动起来
        var passedTime = 0.001 * this.game.time.deltaTime;
        this._armature.advanceTime(passedTime);
    }
    else if (this.animationType === qc.Sprite.FRAME_ANIMATION || this.animationType === qc.Sprite.FRAME_SAMPLES) {
        // 帧动画，如果 timeScale 发生变更，需要重新设置播放速度
        var timeScale = this.game.time.timeScale;
        if (timeScale === this._slowMotion)
            return;

        this._slowMotion = timeScale;
        var animation = this.phaser.animations.currentAnim;
        if (animation) animation.speed = this._speed / timeScale;
    }
};

/**
 * 绑定骨骼动画的事件
 */
Sprite.prototype.bindDragonBonesEvent = function() {
    var self = this;
    self._armature.addEventListener('start', function() {
        if (self._onStart) self._onStart.dispatch(self.lastAnimationName);
    });
    self._armature.addEventListener('complete', function() {
        if (self._onFinished) self._onFinished.dispatch(self.lastAnimationName);
    });
    self._armature.addEventListener('loopComplete', function() {
        if (self._onLoopFinished) self._onLoopFinished.dispatch(self.lastAnimationName);
    });
};

/**
 * 获取动作具体信息
 * @param {animationName} 动作名
 * @returns 动作的具体信息，没有找到返回 undefined
 */
Sprite.prototype.getAnimationInfo = function(animationName) {
    var self = this;
    var animationType = this.animationType;
    if (animationType === Sprite.NONE_ANIMATION) return;

    if (animationType === Sprite.DRAGON_BONES) {
        // 骨骼动画
        var animation = this._armature.animation;
        if (animation.animationNameList.indexOf(animationName) < 0) {
            return;
        }

        var i = animation.animationNameList.length;
        var animationData;
        while (i--) {
            if (animation._animationDataList[i].name == animationName) {
                animationData = animation._animationDataList[i];
                break;
            }
        }
        return animationData;
    }
    else {
        var animationData = this._animation[animationName];
        if (!animationData)
            return;
        var frames = animationData['frames'] || [];
        var rate = animationData['rate'] ? parseFloat(animationData['rate']) : 30;
        return {
            frames : frames,
            frameRate : rate,
            loop : animationData['loop'] ? 0 : 1,
            duration : frames.length / rate
        };
    }
};

Object.defineProperties(Sprite.prototype, {
    /**
     *  @property {qc.Atlas} texture - 图集
     */
    texture : {
        get : function() {
            return this._texture;
        },
        set : function(atlas) {
            // 如果之前是骨骼动画，需要移除掉骨骼
            if (this.animationType === Sprite.DRAGON_BONES &&
                this._boneBase) {
                this.removeChild(this._boneBase);
                this._armature = null;
                this._boneBase = null;
            }

            if (!atlas) {
                this._texture = null;
                this.phaser.loadTexture(null, this.frame);
                this.animationType = Sprite.NONE_ANIMATION;
                return;
            }
            this._texture = atlas;

            // 记录动作信息
            this.animationType = atlas.animation ? atlas.animation.type : Sprite.NONE_ANIMATION;
            if (this.animationType === Sprite.DRAGON_BONES) {
                var armature = self._armature = qc.dragonBones.makeQcArmature(
                    atlas.animation.data,
                    atlas.json,
                    atlas.img, atlas.key);
                if (!armature) {
                    console.warn("生成骨骼失败，参数：（img:", atlas.img,
                        "atlas:", atlas.json, "skeleton:", atlas.animation.data, "）");
                    return;
                }
                this._armature = armature;

                // 添加到世界中
                var bonesBase = armature.getDisplay();
                this.addChild(bonesBase);
                this._boneBase = bonesBase;

                // 集成上个骨骼的 colorTint
                this.colorTint = this.colorTint;
            }
            else if (this.animationType === Sprite.FRAME_ANIMATION) {
                this._animation = atlas.animation.data.animations;
                this.phaser.animations = new Phaser.AnimationManager(this.phaser);
            }
            else if (this.animationType === Sprite.FRAME_SAMPLES) {
                this._animation = atlas.animation.data.samples;
                this.phaser.animations = new Phaser.AnimationManager(this.phaser);
            }

            // 如果frame不存在，则使用第一帧
            var frame = this.frame;
            if (!atlas.getFrame(this.frame)) frame = 0;

            // 载入贴图（通过设置frame来起效）
            this.phaser.key = atlas.key;
            this.frame = frame;
            this.paused = false;

            // 绑定事件监听
            if (this.animationType === Sprite.DRAGON_BONES) {
                this.bindDragonBonesEvent();
            }

            // 如果有默认动作，尝试播放 or 删除之
            if (this.defaultAnimation) {
                if (!this.animationNameList || this.animationNameList.indexOf(this.defaultAnimation) < 0) {
                    // 没有这个动作
                    this.defaultAnimation = null;
                }
                else
                    this.playAnimation(this.defaultAnimation);
            }

            this._dispatchLayoutArgumentChanged('size');

            if (this._onTextureChanged) {
                this._onTextureChanged.dispatch();
            }
        }
    },

    /**
     *  获取or设置当前的图片帧，一般是图集才会用到该属性（可以为数字或别名）
     *  @property {int|string} frame
     */
    frame : {
        get : function() {
            if (!this.texture) return null;
            return this.phaser.frameName;
        },
        set : function(value) {
            if (!this.texture) return;

            this.phaser.loadTexture(this.texture.key, value, false);
            this._dispatchLayoutArgumentChanged('size');

            if (this._onTextureChanged) {
                this._onTextureChanged.dispatch();
            }
        }
    },

    /**
     * @property {string} defaultAnimation - 默认的动作，在资源成功载入后会尝试播放
     * @readonly
     */
    defaultAnimation : {
        get : function() {
            return this._defaultAnimation;
        },
        set : function(value) {
            this._defaultAnimation = value;

            // 尝试下播放
            this.playAnimation(value);
        }
    },

    /**
     * 当前动作是否停止
     * @property {bool} paused
     */
    paused : {
        get : function() {
            return this.isPaused();
        },
        set : function(value) {
            switch (this.animationType) {
            case qc.Sprite.FRAME_ANIMATION :
            case qc.Sprite.FRAME_SAMPLES :
                if (!this.phaser.animations.currentAnim) return;
                this.phaser.animations.paused = value;
                break;
            case qc.Sprite.DRAGON_BONES :
                this._paused = value;
                break;
            default :
                // do nothing
                break;
            }
        }
    },

    /**
     * 当前动作是否播放中
     */
    isPlaying : {
        get : function() {
            switch (this.animationType) {
            case qc.Sprite.FRAME_ANIMATION :
            case qc.Sprite.FRAME_SAMPLES :
                var ani = this.phaser.animations.currentAnim;
                return !!ani && ani.isPlaying;
            case qc.Sprite.DRAGON_BONES :
                return this._armature && this._armature.animation.getIsPlaying();
            default :
                return false;
            }
        }
    },

    /**
     * 当前动作是否结束
     */
    isComplete : {
        get : function() {
            switch (this.animationType) {
            case qc.Sprite.FRAME_ANIMATION :
            case qc.Sprite.FRAME_SAMPLES :
                var ani = this.phaser.animations.currentAnim;
                return !!ani && ani.isFinished;
            case qc.Sprite.DRAGON_BONES :
                return this._armature && this._armature.animation.getIsComplete();
            default :
                return false;
            }
        }
    },

    /**
     * 上一个播放的动作名字
     */
    lastAnimationName : {
        get : function() {
            switch (this.animationType) {
            case qc.Sprite.FRAME_ANIMATION :
            case qc.Sprite.FRAME_SAMPLES :
                var ani = this.phaser.animations.currentAnim;
                if (!ani) return null;
                return ani.name;
            case qc.Sprite.DRAGON_BONES :
                return this._armature && this._armature.animation.getLastAnimationName();
            default :
                return false;
            }
        }
    },

    /**
     * @property {Phaser.Signal} onStart -  动作开始事件
     */
    onStart : {
        get: function() {
            if (!this._onStart) {
                this._onStart = new Phaser.Signal();
            }
            return this._onStart;
        }
    },

    /**
     * @property {Phaser.Signal} onFinished -  动作结束事件
     */
    onFinished : {
        get: function() {
            if (!this._onFinished) {
                this._onFinished = new Phaser.Signal();
            }
            return this._onFinished;
        }
    },

    /**
     * @property {Phaser.Signal} onLoopFinished -  循环动作的单轮播放完毕事件
     */
    onLoopFinished : {
        get: function() {
            if (!this._onLoopFinished) {
                this._onLoopFinished = new Phaser.Signal();
            }
            return this._onLoopFinished;
        }
    },

    /**
     * @property {qc.Rectangle} nativeSize - 图片实际大小
     * @readonly
     */
    nativeSize : {
        get : function() {
            return (this.phaser && this.phaser.texture && this.phaser.texture.crop) || new qc.Rectangle(0, 0, 0, 0);
        }
    },

    /**
     * 获取当前使用的骨骼根节点
     * @property armature
     */
    armature : {
        get : function() {
            return this._armature;
        }
    },

    /**
     * 获取当前可以播放的动作列表
     * @property {array} animationNameList
     */
    animationNameList : {
        get : function() {
            switch (this.animationType) {
            case qc.Sprite.FRAME_ANIMATION :
                if (!this.texture)
                    return null;
                var allAnimationData = this.texture.animation.data.animations;
                if (!allAnimationData)
                    return null;
                return Object.keys(allAnimationData);
            case qc.Sprite.DRAGON_BONES :
                if (!this._armature)
                    return null;
                return this._armature.animation.animationNameList;
            case qc.Sprite.FRAME_SAMPLES :
                var data = this._animation;
                if (!data) return null;
                return Object.keys(data);
            default :
                return null;
            }
        }
    },

    /**
     * 设置颜色混合
     * @property {qc.Color} colorTint
     */
    colorTint : {
        get : function() {
            return new Color(this.phaser.tint);
        },

        set : function(value) {
            if (!(value instanceof Color))
                throw new Error('Expected:qc.Color');

            this.phaser.tint = value.toNumber();

            // 如果是骨骼动作，需要遍历进去
            if (this.animationType === qc.Sprite.DRAGON_BONES &&
                this._armature) {
                this._armature._slotList.forEach(function(slot) {
                    if (slot._displayList) {
                        slot._displayList.forEach(function(image) {
                            image.colorTint = value;
                        });
                    }
                });
            }
        }
    },

    /**
     * @property {Phaser.Signal} onTextureChanged - 当显示的贴图发生变化时触发
     */
    onTextureChanged : {
        get : function() {
            if (!this._onTextureChanged) {
                this._onTextureChanged = new Phaser.Signal();
            }
            return this._onTextureChanged;
        }
    },

    /**
     * @property {string} class - 类名
     * @internal
     * @readonly
     */
    class : {
        get : function() { return 'qc.Sprite'; }
    }
});

/**
 * 设置图片大小为实际大小
 * @method qc.Sprite#resetNativeSize
 */
Sprite.prototype.resetNativeSize = function() {
    this.width = this.nativeSize.width;
    this.height = this.nativeSize.height;
};

/**
 * 动作是否处于暂停状态
 * @method qc.Sprite#isPaused
 */
Sprite.prototype.isPaused = function() {
    switch (this.animationType) {
    case qc.Sprite.FRAME_ANIMATION :
    case qc.Sprite.FRAME_SAMPLES :
        if (!this.phaser.animations.currentAnim) return false;
        return this.phaser.animations.paused;
    case qc.Sprite.DRAGON_BONES :
        return this._paused;
    default :
        return false;
    }
};

/**
 * @hackpp
 * 此处 hack phaser 的 set frame 方法，因为 phaesr 播放帧动画时候，调用
 * setFrame 方法，而我们希望其对应的 frame 大小应该是我们在 inspector 中设置
 * 的 width/height，故 hack 后多做一些事情
 */
var setFrame = function (frame) {
    var sprite = this._qc;

    // 原有方法调用
    sprite._phaserImageSetFrame.call(this, frame);

    // 非骨骼动画，set frame 之后，做一下 setWidth/setHeight 方法
    if (sprite.animationType !== qc.Sprite.DRAGON_BONES) {
        sprite.setWidth(sprite.width);
        sprite.setHeight(sprite.height);
    }
};


var _spriteWebGLAddQuad = function(spriteBatch, sprite, w0, h0, w1, h1, w2, h2, w3, h3, uvx0, uvy0, uvx1, uvy1, a, b, c, d, tx, ty, tint) {
    if(spriteBatch.currentBatchSize >= spriteBatch.size)
    {
        spriteBatch.flush();
        spriteBatch.currentBaseTexture = sprite.texture.baseTexture;
    }

    var colors = spriteBatch.colors;
    var positions = spriteBatch.positions;
    var index = spriteBatch.currentBatchSize * 4 * spriteBatch.vertSize;

    if(spriteBatch.renderSession.roundPixels)
    {
        positions[index] = a * w0 + c * h0 + tx | 0;
        positions[index+1] = d * h0 + b * w0 + ty | 0;
        positions[index+5] = a * w1 + c * h1 + tx | 0;
        positions[index+6] = d * h1 + b * w1 + ty | 0;
        positions[index+10] = a * w2 + c * h2 + tx | 0;
        positions[index+11] = d * h2 + b * w2 + ty | 0;
        positions[index+15] = a * w3 + c * h3 + tx | 0;
        positions[index+16] = d * h3 + b * w3 + ty | 0;
    }
    else
    {
        positions[index] = a * w0 + c * h0 + tx;
        positions[index+1] = d * h0 + b * w0 + ty;
        positions[index+5] = a * w1 + c * h1 + tx;
        positions[index+6] = d * h1 + b * w1 + ty;
        positions[index+10] = a * w2 + c * h2 + tx;
        positions[index+11] = d * h2 + b * w2 + ty;
        positions[index+15] = a * w3 + c * h3 + tx;
        positions[index+16] = d * h3 + b * w3 + ty;
    }

    positions[index+2] = uvx0;
    positions[index+3] = uvy0;
    positions[index+7] = uvx1;
    positions[index+8] = uvy0;
    positions[index+12] = uvx1;
    positions[index+13] = uvy1;
    positions[index+17] = uvx0;
    positions[index+18] = uvy1;

    colors[index+4] = colors[index+9] = colors[index+14] = colors[index+19] = tint;

    // increment the batchsize
    spriteBatch.sprites[spriteBatch.currentBatchSize++] = sprite;

};

/**
 * hack sprite 的 _renderWebGL 方法为了让骨骼动画的根节点不显示
 * @hackpp
 */
var spriteRenderWebGL = function(renderSession) {
    var _qc = this._qc;
    var animationType = _qc.animationType;

    if (animationType === qc.Sprite.FRAME_SAMPLES) {
        if (!this.visible || this.alpha <= 0 || !this.renderable) return;
        if (!this.animations.currentAnim) return;
        var spriteBatch = renderSession.spriteBatch;
        var sprite = this;

        var frameIndex = this.animations.currentAnim._frameIndex;

        var animationData = _qc.texture.animation.data;
        var uvs = animationData.uvs;
        var lastAnimationName = _qc.lastAnimationName;
        var lastAnimationInfo = _qc._animation[lastAnimationName];
        var curAnimationFrames = lastAnimationInfo.frames;
        var texture = sprite.texture;
        var resolution = texture.baseTexture.resolution;
        var worldTransform = sprite.worldTransform;
        var sa = worldTransform.a / resolution;
        var sb = worldTransform.b / resolution;
        var sc = worldTransform.c / resolution;
        var sd = worldTransform.d / resolution;
        var stx = worldTransform.tx;
        var sty = worldTransform.ty;
        var tint = sprite.tint;
        var parentAlpha = sprite.parent.worldAlpha;
        var last = lastAnimationInfo.last;
        var ibegin, uvbegin, ibegin2, uvmap;

        // 处理 tint（alpha后续不同部位都不同，需要单独处理）
        tint = (tint >> 16) + (tint & 0xff00) + ((tint & 0xff) << 16);

        var intIndex = -1;
        var id1, id2;
        var convert;
        var dealCacheList;

        // 处理帧的预处理缓存
        if (!animationData.convert) animationData.convert = {};
        if (!animationData.convert[lastAnimationName])
            animationData.convert[lastAnimationName] = convert = new Array(curAnimationFrames.length);
        else
            convert = animationData.convert[lastAnimationName];

        if ((frameIndex & 0x7) === 0) {
            intIndex = (frameIndex >> 3);
            if (!convert[intIndex]) dealCacheList = [intIndex];
        }
        else if (frameIndex === last - 1) {
            intIndex = (frameIndex >> 3) + 1;
            if (!convert[intIndex]) dealCacheList = [intIndex];
        }
        else {
            id1 = frameIndex >> 3;
            id2 = id1 + 1;
            if (!convert[id1] && !convert[id2]) dealCacheList = [ id1, id2 ];
            else if (!convert[id1]) dealCacheList = [ id1 ];
            else if (!convert[id2]) dealCacheList = [ id2 ];
        }

        var dw0, dh0, dw1, dh1, dw2, dh2, dw3, dh3;
        var alpha;

        if (dealCacheList) {
            var width = sprite.texture.baseTexture.width;
            var height = sprite.texture.baseTexture.height;
            var uvmax= uvs.length >> 2;
            for (var dealIndex = 0, dealLen = dealCacheList.length; dealIndex < dealLen; dealIndex++) {
                var dealID = dealCacheList[dealIndex];
                var frameData = curAnimationFrames[dealID];
                if (!frameData) {
                    console.log(dealID, dealCacheList);
                    continue;
                }
                uvmap = new Array(uvmax);
                for (var i = 0, len = frameData.length / 10; i < len; i++) {
                    ibegin = i * 10;
                    uvbegin = frameData[ibegin] * 4;
                    uvmap[uvbegin] = i;

                    var la = frameData[ibegin + 1];
                    var lb = frameData[ibegin + 2];
                    var lc = frameData[ibegin + 3];
                    var ld = frameData[ibegin + 4];
                    var ltx = frameData[ibegin + 5];
                    var lty = frameData[ibegin + 6];
                    var px = frameData[ibegin + 7];
                    var py = frameData[ibegin + 8];
                    var lw = (uvs[uvbegin + 2] - uvs[uvbegin]) * width;
                    var lh = (uvs[uvbegin + 3] - uvs[uvbegin + 1]) * height;

                    // 定位点
                    var w0 = lw * -px;
                    var h0 = lh * -py;
                    var w1 = lw * (1 - px);
                    var h1 = lh * (1 - py);

                    frameData[ibegin + 1] = la * w0 + lc * h0 + ltx;
                    frameData[ibegin + 2] = lb * w0 + ld * h0 + lty;
                    frameData[ibegin + 3] = la * w1 + lc * h0 + ltx;
                    frameData[ibegin + 4] = lb * w1 + ld * h0 + lty;
                    frameData[ibegin + 5] = la * w1 + lc * h1 + ltx;
                    frameData[ibegin + 6] = lb * w1 + ld * h1 + lty;
                    frameData[ibegin + 7] = la * w0 + lc * h1 + ltx;
                    frameData[ibegin + 8] = lb * w0 + ld * h1 + lty;
                }

                animationData.convert[lastAnimationName][dealID] = true;
                if (!animationData.uvmap) animationData.uvmap = {};
                if (!animationData.uvmap[lastAnimationName]) animationData.uvmap[lastAnimationName] = {};
                animationData.uvmap[lastAnimationName][dealID] = uvmap;
            }
        }

        if (intIndex >= 0)
        {
            // 不需要进行插值的显示
            var frameData = curAnimationFrames[intIndex];
            for (var i = 0, len = frameData.length / 10; i < len; i++) {
                ibegin = i * 10;
                uvbegin = frameData[ibegin] * 4;

                dw0 = frameData[ibegin + 1];
                dh0 = frameData[ibegin + 2];
                dw1 = frameData[ibegin + 3];
                dh1 = frameData[ibegin + 4];
                dw2 = frameData[ibegin + 5];
                dh2 = frameData[ibegin + 6];
                dw3 = frameData[ibegin + 7];
                dh3 = frameData[ibegin + 8];

                alpha = frameData[ibegin + 9];

                _spriteWebGLAddQuad(spriteBatch, sprite,
                    dw0, dh0, dw1, dh1, dw2, dh2, dw3, dh3,
                    uvs[uvbegin],
                    uvs[uvbegin + 1],
                    uvs[uvbegin + 2],
                    uvs[uvbegin + 3],
                    sa, sb, sc, sd, stx, sty,
                    tint + ((alpha * parentAlpha) << 24));
            }
        }
        else {
            // 本帧需要进行线性插值
            var co = (frameIndex - (id1 << 3)) / (id2 === last ? last - (id1 << 3) : 8);

            var frameData = curAnimationFrames[id1];
            var frameData2 = curAnimationFrames[id2];
            var uvmap = animationData.uvmap[lastAnimationName][id2];
            for (var i = 0, len = frameData.length / 10; i < len; i++) {
                ibegin = i * 10;
                uvbegin = frameData[ibegin] * 4;
                ibegin2 = uvmap[uvbegin] * 10;
                if (isNaN(ibegin2)) {
                    // 这个节点在下个动作帧中无法找到，就不进行插值了
                    dw0 = frameData[ibegin + 1];
                    dh0 = frameData[ibegin + 2];
                    dw1 = frameData[ibegin + 3];
                    dh1 = frameData[ibegin + 4];
                    dw2 = frameData[ibegin + 5];
                    dh2 = frameData[ibegin + 6];
                    dw3 = frameData[ibegin + 7];
                    dh3 = frameData[ibegin + 8];
                    alpha = frameData[ibegin + 9];
                }
                else {
                    // 进行现行插值
                    dw0 = frameData[ibegin + 1];
                    dh0 = frameData[ibegin + 2];
                    dw1 = frameData[ibegin + 3];
                    dh1 = frameData[ibegin + 4];
                    dw2 = frameData[ibegin + 5];
                    dh2 = frameData[ibegin + 6];
                    dw3 = frameData[ibegin + 7];
                    dh3 = frameData[ibegin + 8];
                    alpha = frameData[ibegin + 9];
                    dw0 = dw0 + (frameData2[ibegin2 + 1] - dw0) * co;
                    dh0 = dh0 + (frameData2[ibegin2 + 2] - dh0) * co;
                    dw1 = dw1 + (frameData2[ibegin2 + 3] - dw1) * co;
                    dh1 = dh1 + (frameData2[ibegin2 + 4] - dh1) * co;
                    dw2 = dw2 + (frameData2[ibegin2 + 5] - dw2) * co;
                    dh2 = dh2 + (frameData2[ibegin2 + 6] - dh2) * co;
                    dw3 = dw3 + (frameData2[ibegin2 + 7] - dw3) * co;
                    dh3 = dh3 + (frameData2[ibegin2 + 8] - dh3) * co;
                    alpha = alpha + (frameData2[ibegin2 + 9] - alpha) * co;
                }

                _spriteWebGLAddQuad(spriteBatch, sprite,
                    dw0, dh0, dw1, dh1, dw2, dh2, dw3, dh3,
                    uvs[uvbegin],
                    uvs[uvbegin + 1],
                    uvs[uvbegin + 2],
                    uvs[uvbegin + 3],
                    sa, sb, sc, sd, stx, sty,
                    tint + ((alpha * parentAlpha) << 24));
            }
        }

        return PIXI.DisplayObjectContainer.prototype._renderWebGL.call(this, renderSession);

    }
    else if (animationType === qc.Sprite.DRAGON_BONES && _qc._armature) {
        // 骨骼动画
        return PIXI.DisplayObjectContainer.prototype._renderWebGL.call(this, renderSession);
    }
    else {
        // 普通动画
        return Phaser.Sprite.prototype._renderWebGL.call(this, renderSession);
    }
};

/**
 * hack sprite 的 _renderCanva 方法为了让骨骼动画的根节点不显示
 * @hackpp
 */
var spriteRenderCanvas = function(renderSession) {
    var _qc = this._qc;
    var animationType = _qc.animationType;

    if (animationType === qc.Sprite.FRAME_SAMPLES) {
        if (this.visible === false || this.alpha === 0 || this.renderable === false || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) return;
        if (!this.animations.currentAnim) return;

        var context = renderSession.context;

        if (this.blendMode !== renderSession.currentBlendMode)
        {
            renderSession.currentBlendMode = this.blendMode;
            context.globalCompositeOperation = PIXI.blendModesCanvas[renderSession.currentBlendMode];
        }
        if (this._mask) renderSession.maskManager.pushMask(this._mask, renderSession);

        //  Ignore null sources
        if (this.texture.valid)
        {
            var resolution = this.texture.baseTexture.resolution / renderSession.resolution;

            //  If smoothingEnabled is supported and we need to change the smoothing property for this texture
            ////---- Hackpp here resize 时 context 中的平滑属性会被变更，需要重新设置
            if (renderSession.smoothProperty &&
                (renderSession.scaleMode !== this.texture.baseTexture.scaleMode ||
                context[renderSession.smoothProperty] !== (renderSession.scaleMode === PIXI.scaleModes.LINEAR))) {
                renderSession.scaleMode = this.texture.baseTexture.scaleMode;
                context[renderSession.smoothProperty] = (renderSession.scaleMode === PIXI.scaleModes.LINEAR);
            }

            var texture;
            var width = this.texture.baseTexture.width;
            var height = this.texture.baseTexture.height;

            if (this.tint !== 0xFFFFFF) {
                if (this.cachedTint !== this.tint) {
                    this.cachedTint = this.tint;
                    var crop = this.texture.crop;
                    var cx = crop.x;
                    var cy = crop.y;
                    var cw = crop.width;
                    var ch = crop.height;
                    crop.x = 0;
                    crop.y = 0;
                    crop.width = width;
                    crop.height = height;
                    this.tintedTexture = PIXI.CanvasTinter.getTintedTexture(this, this.tint);
                    crop.x = cx;
                    crop.y = cy;
                    crop.width = cw;
                    crop.height = ch;
                }
                texture = this.tintedTexture;
            }
            else {
                texture = this.texture.baseTexture.source;
            }

            var sprite = this;
            var frameIndex = this.animations.currentAnim._frameIndex;
            var animationData = _qc.texture.animation.data;
            var uvs = animationData.uvs;
            var lastAnimationName = _qc.lastAnimationName;
            var curAnimationFrames = _qc._animation[lastAnimationName].frames;
            var imageResolution = sprite.texture.baseTexture.resolution;
            var worldTransform = sprite.worldTransform;
            var sa = worldTransform.a / imageResolution;
            var sb = worldTransform.b / imageResolution;
            var sc = worldTransform.c / imageResolution;
            var sd = worldTransform.d / imageResolution;
            var stx = worldTransform.tx;
            var sty = worldTransform.ty;
            var last = _qc._animation[lastAnimationName].last;
            var intIndex, id1, id2, convert, uvmap, frameData, frameData2, co, ibegin, uvbegin, ibegin2;

            if ((frameIndex & 0x7) === 0) {
                intIndex = (frameIndex >> 3);
                frameData = curAnimationFrames[intIndex];
            }
            else if (frameIndex === last - 1) {
                intIndex = (frameIndex >> 3) + 1;
                frameData = curAnimationFrames[intIndex];
            }
            else {
                id1 = frameIndex >> 3;
                id2 = id1 + 1;

                // 需要根据前后帧进行插值
                frameData = curAnimationFrames[id1];
                frameData2 = curAnimationFrames[id2];

                co = (frameIndex - (id1 << 3)) / (id2 === last ? last - (id1 << 3) : 8);

                // 需确保 id2 对应的 uvmap 已存在
                if (!animationData.uvmap) animationData.uvmap = {};
                if (!animationData.uvmap[lastAnimationName])
                    animationData.uvmap[lastAnimationName] = convert = new Array(curAnimationFrames.length);
                else
                    convert = animationData.uvmap[lastAnimationName];

                if (!convert[id2]) {
                    var uvmax= uvs.length >> 2;
                    uvmap = new Array(uvmax);
                    frameData2 = curAnimationFrames[id2];
                    for (var i = 0, len = frameData2.length / 10; i < len; i++) {
                        uvbegin = frameData2[i * 10] * 4;
                        uvmap[uvbegin] = i;
                    }
                    convert[id2] = uvmap;
                }
                else
                    uvmap = convert[id2];
            }

            var palpha = this.parent.worldAlpha;

            for (var i = 0, len = frameData.length / 10; i < len; i++) {
                ibegin = i * 10;
                uvbegin = frameData[ibegin] * 4;

                var la = frameData[ibegin + 1];
                var lb = frameData[ibegin + 2];
                var lc = frameData[ibegin + 3];
                var ld = frameData[ibegin + 4];
                var ltx = frameData[ibegin + 5];
                var lty = frameData[ibegin + 6];
                var px = frameData[ibegin + 7];
                var py = frameData[ibegin + 8];
                var ialpha = frameData[ibegin + 9];

                // 考虑线性插值
                if (id2) {
                    ibegin2 = uvmap[uvbegin] * 10;
                    if (!isNaN(ibegin2)) {
                        la = la + (frameData2[ibegin2 + 1] - la) * co;
                        lb = lb + (frameData2[ibegin2 + 2] - lb) * co;
                        lc = lc + (frameData2[ibegin2 + 3] - lc) * co;
                        ld = ld + (frameData2[ibegin2 + 4] - ld) * co;
                        ltx = ltx + (frameData2[ibegin2 + 5] - ltx) * co;
                        lty = lty + (frameData2[ibegin2 + 6] - lty) * co;
                        px = px + (frameData2[ibegin2 + 7] - px) * co;
                        py = py + (frameData2[ibegin2 + 8] - py) * co;
                        ialpha = ialpha + (frameData2[ibegin2 + 9] - ialpha) * co;
                    }
                }

                var uv0 = uvs[uvbegin] * width;
                var uv1 = uvs[uvbegin + 1] * height;
                var uv2 = uvs[uvbegin + 2] * width;
                var uv3 = uvs[uvbegin + 3] * height;

                var fa = la * sa + lb * sc;
                var fb = la * sb + lb * sd;
                var fc = lc * sa + ld * sc;
                var fd = lc * sb + ld * sd;
                var ftx = (ltx * sa + lty * sc + stx) * renderSession.resolution;
                var fty = (ltx * sb + lty * sd + sty) * renderSession.resolution;

                var frameWidth = uv2 - uv0;
                var frameHeight = uv3 - uv1;

                // 定位点
                var dx = frameWidth * -px;
                var dy = frameHeight * -py;

                // 设置矩阵
                if (renderSession.roundPixels) {
                    context.setTransform(fa, fb, fc, fd, ftx | 0, fty | 0);
                    dx = dx | 0;
                    dy = dy | 0;
                }
                else {
                    context.setTransform(fa, fb, fc, fd, ftx, fty);
                }

                // 绘制图形
                context.globalAlpha = palpha * (ialpha / 255);
                context.drawImage(texture,
                    uv0, uv1, frameWidth, frameHeight,
                    dx / resolution, dy / resolution, frameWidth / resolution, frameHeight / resolution);
            }
        }

        // OVERWRITE
        var children = this.children;
        for (var i = 0; i < children.length; i++)
        {
            children[i]._renderCanvas(renderSession);
        }

        if (this._mask)
        {
            renderSession.maskManager.popMask(renderSession);
        }
    }
    else if (animationType === qc.Sprite.DRAGON_BONES && _qc._armature) {
        // 骨骼动画
        return PIXI.DisplayObjectContainer.prototype._renderCanvas.call(this, renderSession);
    }
    else {
        // 普通动画
        return Phaser.Sprite.prototype._renderCanvas.call(this, renderSession);
    }
};

/**
 * 设置节点的宽度
 * @protected
 * @override
 */
Sprite.prototype.setWidth = function(w) {
    Node.prototype.setWidth.call(this, w);
    if (!this.phaser.texture.trim) {
        this.phaser.texture.frame.width = w;
    }
};

/**
 * 设置节点的高度
 * @protected
 * @override
 */
Sprite.prototype.setHeight = function(h) {
    Node.prototype.setHeight.call(this, h);
    if (!this.phaser.texture.trim) {
        this.phaser.texture.frame.height = h;
    }
};

/**
 * 获取需要被序列化的信息描述
 * @overide
 * @internal
 */
Sprite.prototype.getMeta = function() {
    var s = qc.Serializer;
    var json = qc.Node.prototype.getMeta.call(this);

    // 增加UIImage需要序列化的内容
    json.texture = s.TEXTURE;
    json.frame = s.AUTO;
    json.defaultAnimation = s.STRINGS;

    return json;
};

/**
 * @author luohj
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 显示文本控件
 * @class qc.UIText
 * @extends qc.Node
 * @param {qc.Game} game - A reference to the currently running game.
 * @constructor
 * @internal
 */
var UIText = qc.UIText = function(game, parent, uuid) {
    // 调用基类的初始
    qc.Node.call(this, new Phaser.Group(game.phaser, null), parent, uuid);

    // 创建一个phaser的text
    this.textPhaser = new Phaser.Text(game.phaser);
    this.textPhaser._nqc = this;
    this.textPhaser.wordWrapWidth = this.width;
    this.phaser.addChildAt(this.textPhaser, 0);

    // text的真实宽度
    this.textRealWidth = 0;
    // text的真实高度
    this.textRealHeight = 0;

    // 设置默认字体大小为 12 px
    this.fontSize = 12;

    // 外发光text
    this._glowText = null;

    // 设置节点的名字
    this.name = "UIText";

    // 文本内容
    this._data = "";

    // 保存color列表
    this._colors = [];

    // 设置默认颜色
    this.color = Color.black;

    // 默认设置不渐变
    this._gradient = false;

    // 渐变开始的颜色
    this.startColor = Color.red;
    // 渐变结束的颜色
    this.endColor = Color.black;

    // 字体类型
    this._fontFamily = UIText.SYSTEMFONT;

    this.textPhaser.updateText = UIText._phaserUpdateText;
    this.textPhaser.determineFontProperties = UIText._phaserDetermineFontProperties;

    // 设置阴影参数
    this.shadowBlur = 10;
    this.shadowOffsetX = 3;
    this.shadowOffsetY = 3;
    this.shadowColor = Color.shadow;
    this._enableShadow = false;

    // 是否外发光
    this._enableGlow = false;
    // 外发光颜色默认为红色
    this.glowColor = Color.red;

    // 必须描边颜色，默认Phaser设置了‘black’，该参数并不被qc.Color支持
    this.stroke = Color.black;
    // 描边宽度 默认为0
    this._strokeThickness = 0;

    // 模糊值
    this._glowBlur = 1;

    // 是否换行显示
    this._wrap = false;
    // 换行显示宽度
    this._wrapWidth = this.width;

    // 自动根据文字内容的宽高调整text的宽高
    this._autoSize = false;

    // 高度变化通知
    this._onPhaseTextSizeChange = new Phaser.Signal();
    this._onPhaseTextSizeChange.add(function(width, height) {
        if (this.autoSize) {
            this.width = width;
            this.height = height;
        }
    }, this);

    var restore = uuid !== undefined;
    if (restore !== true) {
        // 更改字体为正常
        this.bold = false;

        // 文本水平对齐方式默认为 左对齐
        this.alignH = UIText.LEFT;
        // 文本垂直对齐方式 默认居中对齐
        this.alignV = UIText.MIDDLE;

        // 超出是否部分是否裁剪掉，默认为裁剪
        this.overflow = true;
    }

};

UIText.prototype = Object.create(qc.Node.prototype);
UIText.prototype.constructor = UIText;

/**
 * @constant 文本对齐方式
 * @type {number}
 */
UIText.LEFT = 0;
UIText.RIGHT = 1;
UIText.CENTER = 2;

UIText.TOP = 0;
UIText.BOTTOM = 1;
UIText.MIDDLE = 2;

UIText.SYSTEMFONT = 0;
UIText.WEBFONT = 1;
UIText.BITMAPFONT = 2;

/**
 * @constant 符号
 * @type {number}
 */
UIText._SYMBOL_COLOR = 0;
UIText._SYMBOL_END_COLOR = 1;

Object.defineProperties(UIText.prototype, {

    /**
     * 设置颜色混合
     * @property {qc.Color} colorTint
     */
    colorTint : {
        get : function() {
            return new Color(this.textPhaser.tint);
        },

        set : function(value) {
            value = value || new Color(0xFFFFFF);
            if (!(value instanceof Color))
                throw new Error('Expected qc.Color');
            this.textPhaser.tint = value.toNumber();
        }
    },

    /**
     * @property autoSize - 自动根据文字内容的宽高调整text的宽高
     */
    'autoSize' : {
        get : function() { return this._autoSize; },
        set : function(v) {
            if (v === this._autoSize) return;
            this._autoSize = v;
            if (v && this.textPhaser) {
                this.width = this.textPhaser.width;
                this.height = this.textPhaser.height;
            }
        }
    },

    /**
     * 字体类型
     */
    'fontFamily' : {
        get : function() { return this._fontFamily; },
        set : function(v) {
            if (this._fontFamily === v) return;
            this._fontFamily = v;

            // 设置默认数据
            this._changeText('');
            this._isTransformDirty = true;
        }
    },

    /**
     * 水平对齐方式
     *
     * @property alignH
     * @type number
     * @value UIText.LEFT（左对齐）、UIText.CENTER（居中对齐）、UIText.RIGHT（右对齐）
     * @default UIText.LEFT
     */
    'alignH' : {
        get : function() {
            return this._alignH;
        },
        set : function(v) {
            if (v === this._alignH) return;

            this._alignH = v;
            this.textPhaser.dirty = true;
            this._isTransformDirty = true;
        }
    },

    /**
     * 垂直对齐方式
     *
     * @property alignV
     * @type number
     * @value UIText.TOP（上对齐）、UIText.MIDDLE（居中对齐）、UIText.BOTTOM（下对齐）
     * @default UIText.MIDDLE
     */
    'alignV' : {
        get : function() {
            return this._alignV;
        },
        set : function(v) {
            if (v === this._alignV) return;

            this._alignV = v;
            this.textPhaser.dirty = true;
            this._isTransformDirty = true;
        }
    },

    /**
     * 设置字体
     * @property font
     * @type string
     * @vale Serif Sans-serif Monospace Cursive Fantasy
     * @default Arial
     */
    'font' : {
        get : function() {
            var fontName = this.textPhaser.font.replace(/\'/g, "");
            if(this.fontFamily !== UIText.SYSTEMFONT)
                return this.game.assets.find(fontName);
            return fontName;
        },
        set : function(font) {
            if (!font) return;
            var fontName = font;
            if (this.fontFamily !== UIText.SYSTEMFONT) {
                if (!(font instanceof qc.Font)) {
                    this.game.log.error('Expected qc.Font');
                    return;
                }
                if (this.fontFamily !== font._fontFamily) {
                    this.game.log.error('Invalid font asset');
                    return;
                }

                // webfont采用uuid注册到css中，bitmap在pixi中的索引对应的是url
                fontName = (this.fontFamily === UIText.WEBFONT ? font.uuid : font.url)
            }
            else {
                if (typeof font !== 'string') {
                    this.game.log.error('Expected string');
                    return;
                }
            }

            if (fontName !== this.textPhaser.font) {
                this._changeText(fontName);
                if (this.enableGlow) {
                    this._glowText.font = fontName;
                }
                this._isTransformDirty = true;
            }
        }
    },

    /**
     *设置字体是否为粗体
     * @property bold
     * @type boolean
     * @default false
     */
    'bold' : {
        get : function() {
            return this.textPhaser.fontWeight !== "normal";
        },
        set : function(v) {
            var type = v ? "bold" : "normal";
            if (type === this.textPhaser.fontWeight)
                return;

            this.textPhaser.fontWeight = type;
            if (this._enableGlow) {
                this._glowText.fontWeight = type;
            }
            this._isTransformDirty = true;
        }
    },

    /**
     *设置字体大小
     *
     * @property fontSize
     * @type number
     * @value 10 单位是px
     */
    'fontSize' : {
        get : function() {
            return this.textPhaser.fontSize;
        },
        set : function(v) {
            if (typeof v !== 'number') throw new Error('Expected number');
            if (v === this.textPhaser.fontSize)
                return;

            this.textPhaser.fontSize = v;
            if (this._enableGlow) {
                this._glowText.fontSize = v;
            }
            this._isTransformDirty = true;
        }
    },

    /**
     * 设置字体颜色
     * @property color
     * @type {qc.Color}
     */
    'color' : {
        get : function() {
            return new Color(this.textPhaser.fill);
        },
        set : function(v) {
            if (!(v instanceof Color))
                throw new Error('Expected qc.Color');

            var value = v.toString('rgb');
            if (value === this.textPhaser.fill)
                return;

            this.textPhaser.fill = value;
            this._setTextColor(this.textPhaser);
            if (this._enableGlow) {
                this._glowText.fill = value;
            }
            this._isTransformDirty = true;
        }
    },

    /**
     * 设置文本内容
     *
     * @property text
     * @type string
     * @explain 使用范例[#FF0000]变色[-]也可以[#00FF00]嵌[#0000FF]套[-][-]使用
     */
    'text' : {
        get : function() {
            return this._data;
        },
        set : function(v) {
            if (typeof v !== "string")
                throw new Error("Expected string");
            if (v === this._data)
                return;

            //清空自己缓存的colos
            this._colors = [];

            this._data = v;
            this._parseTextColor(v);
            this._setTextColor(this.textPhaser);

            if (this._enableGlow) {
                this._glowText.text = this.textPhaser.text;
            }
            this._isTransformDirty = true;
        }
    },

    /**
     * 设置行间距
     *
     * @property lineSpacing
     * @type number 单位px
     */
    'lineSpacing' : {
        get : function() {
            return this.textPhaser.lineSpacing;
        },
        set : function(v) {
            if (v === this.textPhaser.lineSpacing)
                return;

            this.textPhaser.lineSpacing = v;
            if (this._enableGlow) {
                this._glowText.lineSpacing = v;
            }
            this._isTransformDirty = true;
        }
    },

    /**
     * 设置换行显示
     * @type boolean
     */
    'wrap' : {
        get : function() {
            return this._wrap;
        },
        set : function(v) {
            if (v === this._wrap)
                return;

            this._wrap = v;
            this.textPhaser.wordWrap = v;
            if (this._enableGlow)
                this._glowText.wordWrap = v;
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {boolean} overflow - 文字显示不下时的处理：如果为TRUE则不裁切，否则裁切
     * @default false
     */
    overflow : {
        get : function() {
            var script = this.getScript('qc.NodeMask');
            if (script && !script.enable) return true;
            return false;
        },
        set : function(v) {
            var script = this.getScript('qc.NodeMask');
            if (!script) script = this.addScript('qc.NodeMask');
            script.enable = !v;
        }
    },

    /**
     * @property {number} pivotX - 节点自身的原点X位置
     * 0为左边，1为右边
     */
    pivotX : {
        get : function() {
            return Object.getOwnPropertyDescriptor(qc.Node.prototype, 'pivotX').get.call(this);
        },
        set : function(v) {
            Object.getOwnPropertyDescriptor(qc.Node.prototype, 'pivotX').set.call(this, v);
            if (this.textPhaser) {
                this.textPhaser.dirty = true;
                this._adjustTextPos();
            }
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {number} pivotY - 节点自身的原点Y位置
     * 0为左边，1为右边
     */
    pivotY : {
        get : function() {
            return Object.getOwnPropertyDescriptor(qc.Node.prototype, 'pivotY').get.call(this);
        },
        set : function(v) {
            Object.getOwnPropertyDescriptor(qc.Node.prototype, 'pivotY').set.call(this, v);
            if (this.textPhaser) {
                this.textPhaser.dirty = true;
                this._adjustTextPos();
            }
            this._isTransformDirty = true;
        }
    },

    /**
     *  获取或设置文本的宽度（重载掉父类 Node width 属性）
     *  @property {number} width
     *  @override
     */
    'width': {
        get: function () {
            return Object.getOwnPropertyDescriptor(qc.Node.prototype, 'width').get.call(this);
        },
        set: function (v) {
            if (v === this.width) return;
            Object.getOwnPropertyDescriptor(qc.Node.prototype, 'width').set.call(this, v);

            this._wrapWidth = v;

            if (this._enableGlow)
                this._glowText.wordWrapWidth = v;
            if (this.textPhaser) {
                this.textPhaser.wordWrapWidth = v;
                this._adjustTextPos();
            }
            this._isTransformDirty = true;
        }
    },

    /**
     *  获取或设置文本的高度（重载掉父类 Node height属性）
     *  @property {number} height
     */
    'height': {
        get: function () {
            return Object.getOwnPropertyDescriptor(qc.Node.prototype, 'height').get.call(this);
        },
        set: function (v) {
            if (v === this.height) return;
            Object.getOwnPropertyDescriptor(qc.Node.prototype, 'height').set.call(this, v);
            if (this.textPhaser) {
                this.textPhaser.dirty = true;
                this._adjustTextPos();
            }
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {string} class - 类名字
     * @readonly
     * @internal
     */
    'class' : {
        get : function() { return 'qc.UIText' }
    },

    /**
     * @property {qc.Color} startColor - 渐变开始的颜色
     */
    'startColor' : {
        get : function() { return new Color(this._startColor); },
        set : function(v) {
            if (!(v instanceof Color))
                throw new Error('Expected qc.Color');

            var value = v.toString();
            if (value === this._startColor)
                return;

            this._startColor = value;
            this.textPhaser.dirty = true;
        }
    },

    /**
     * @property {qc.Color} endColor - 渐变结束的颜色
     */
    'endColor' : {
        get : function() { return new Color(this._endColor); },
        set : function(v) {
            if (!(v instanceof Color))
                throw new Error('Expected qc.Color');

            var value = v.toString();
            if (value === this._endColor)
                return;

            this._endColor = value;
            this.textPhaser.dirty = true;
        }
    },

    /**
     * @property {boolean} gradient - 是否开启渐变功能
     */
    'gradient' : {
        get : function() { return this._gradient; },
        set : function(v) {
            if (v === this._gradient)
                return;

            this._gradient = v;
            this.textPhaser.dirty = true;
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {qc.Color} stroke - 描边颜色
     */
    'stroke' : {
        get : function() { return new Color(this.textPhaser.stroke); },
        set : function(v) {
            if (!(v instanceof Color))
                throw new Error('Expected qc.Color');

            var value = v.toString();
            if (value === this.textPhaser.stroke)
                return;

            this.textPhaser.stroke = value;
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {number} strokeThickness - 描边粗度
     */
    'strokeThickness' : {
        get : function() {
            return this._strokeThickness;
        },
        set : function(v) {
            if (v === this._strokeThickness)
                return;

            this._strokeThickness = v;

            if (this._enableGlow) {
                this._glowText.strokeThickness = this.textPhaser.strokeThickness;
                this._glowText.dirty = true;
            }

            this.textPhaser.dirty = true;
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {boolean} enableShadow - 是否显示阴影
     */
    'enableShadow' : {
        get : function() {
            return this._enableShadow;
        },
        set : function(v) {
            this._enableShadow = v;
            this.textPhaser.dirty = true;
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {qc.Color} shadowColor - 阴影颜色
     */

    'shadowColor' : {
        get : function() { return new Color(this.textPhaser.shadowColor); },
        set : function(v) {
            if (!(v instanceof Color))
                throw new Error('Expected qc.Color');

            var value = v.toString();
            if (value === this.textPhaser.shadowColor)
                return;

            this.textPhaser.shadowColor = value;
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {number} shadowBlur - 高斯模糊值
     */
    'shadowBlur' : {
        get : function() { return this.textPhaser.shadowBlur; },
        set : function(v) {
            if (v === this.textPhaser.shadowBlur)
                return;

            this.textPhaser.shadowBlur = v;
            this._isTransformDirty = true;
        }
     },

    /**
     * @property {number} shadowOffsetX - 阴影偏移量x
     */
    'shadowOffsetX' : {
        get : function() {
            return this.textPhaser.shadowOffsetX;
        },
        set : function(v) {
            this.textPhaser.shadowOffsetX = v;
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {number} shadowOffsetY - 阴影偏移量y
     */
    'shadowOffsetY' : {
        get : function() {
            return this.textPhaser.shadowOffsetY;
        },
        set : function(v) {
            this.textPhaser.shadowOffsetY = v;
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {qc.Color} glowColor - 外发光颜色
     */
    'glowColor' : {
        get : function() { return new Color(this._glowColor); },
        set : function(v) {
            if (!(v instanceof Color))
                throw new Error('Expected qc.Color');

            var value = v.toString();
            if (value === this._glowColor)
                return;

            this._glowColor = value;
            if (this._glowText) {
                this._glowText.fill = value;
                this._glowText.stroke = value;
                this._glowText.dirty = true;
            }
        }
    },

    /**
     * @property {number} glowBlur - 外发光模糊值
     */
    'glowBlur' : {
        get : function() { return this._glowBlur; },
        set : function(v) {
            if (v === this._glowBlur) return;
            this._glowBlur = v;
            if (this.enableGlow) {
                this._glowText.filterX.blur = v;
                this._glowText.filterY.blur = v;
            }
        }
    },

    /**
     * @property {boolean} enableGlow - 外发光
     */
    'enableGlow' : {
        get : function() { return this._enableGlow; },
        set : function(v) {
            if (v === this._enableGlow)
                return;

            this._enableGlow = v;
            if (v) {
                this._glowText = new Phaser.Text(this.game.phaser);
                this._glowText._nqc = this;
                this._copyProperty(this._glowText, this.textPhaser);
                this._glowText.x = this.textPhaser.x;
                this._glowText.y = this.textPhaser.y;

                this._glowText.fill = this._glowColor;
                this._glowText.stroke = this._glowColor;

                this._glowText.shadowColor = this._glowColor;

                this._glowText.scale = this.textPhaser.scale;
                this._glowText.pivot = this.textPhaser.pivot;
                this._glowText.rotation = this.textPhaser.rotation;

                this._glowText.updateText = UIText._phaserUpdateText;
                this._glowText.determineFontProperties = UIText._phaserDetermineFontProperties;

                this._glowText.font = this.textPhaser.font;

                this.phaser.addChildAt(this._glowText, 0);

                var filterX = new qc.Filter.BlurX(this.game);
                var filterY = new qc.Filter.BlurY(this.game);
                filterX.blur = this.glowBlur;
                filterY.blur = this.glowBlur;

                this._glowText.filters = [filterX, filterY];
                this._glowText.filterX = filterX;
                this._glowText.filterY = filterY;
            }
            else {
                this.phaser.removeChild(this._glowText);
                this._glowText.destroy();
                this._glowText = null;
                this.textPhaser.dirty = true;
            }
            this._isTransformDirty = true;
        }
    },

    /**
     * @property {qc.Rectangle} nativeSize - text实际大小
     * @readonly
     */
    nativeSize : {
        get : function() {
            var rect = this.rect;
            rect.width = this.textRealWidth;
            rect.height = this.textRealHeight;
            return rect;
        }
    }
});

/**
 * 切换text 和 bitmapText
 * @param {string} font
 * @private
 */
UIText.prototype._changeText = function(font) {
    var isBitmapText = PIXI.BitmapText.fonts[font];

    var text ;
    if (isBitmapText) {
        text = new Phaser.BitmapText(this.game.phaser, 0, 0, font);
        if (this._glowText)
            this._glowText.visible = false;
    }
    else {
        text = new Phaser.Text(this.game.phaser);
        text.updateText = UIText._phaserUpdateText;
        text.determineFontProperties = UIText._phaserDetermineFontProperties;
        if (this._glowText) {
            this._glowText.visible = true;
        }
    }

    text.font = font;

    text._nqc = this;

    // 字体重置为默认
    // text属性设置为上一个text的属性
    this._copyProperty(text, this.textPhaser);
    text.x = this.textPhaser.x;
    text.y = this.textPhaser.y;
    if (this._glowText) {
        this._glowText.font = text.font;
    }

    // 获取在父节点中得索引位置，并且将它移除
    var index = this.phaser.getChildIndex(this.textPhaser);
    if (this.phaser.removeAt) {
        this.phaser.removeAt(index);
    }
    else {
        this.phaser.removeChildAt(index);
    }

    // 替换成新的text
    // Phaser.Group类型采用addAt
    if (this.phaser.addAt) {
        this.phaser.addAt(text, index);
    }
    else {
        this.phaser.addChildAt(text, index);
    }

    this.textPhaser.destroy();
    this.textPhaser = text;
    this._isTransformDirty = true;
    this._setTextColor(this.textPhaser);
};

/**
 * 设置节点的宽度，可能会被重载，这接口不会引起重排
 * @overide
 * @protected
 */
UIText.prototype.setWidth = function(w) {
    qc.Node.prototype.setWidth.call(this, w);

    this._wrapWidth = w;
    if (this.textPhaser) {
        this.textPhaser.wordWrapWidth = w;
        this._adjustTextPos();
    }
};

/**
 * 设置节点的高度，可能会被重载，这接口不会引起重排
 * @overide
 * @protected
 */
UIText.prototype.setHeight = function(h) {
    qc.Node.prototype.setHeight.call(this, h);
    if (this.textPhaser) {
        this.textPhaser.dirty = true;
        this._adjustTextPos();
    }
};

/**
 * 拷贝属性
 * @method _copyProperty
 * @param {Phaser.Text} source
 * @param {Phaser.Text} target
 * @private
 */
UIText.prototype._copyProperty = function(source, target) {
    source.align = target.align;
    source.fontWeight = target.fontWeight;
    source.fontSize = target.fontSize;
    source.lineSpacing = target.lineSpacing;
    source.wordWrap = this._wrap;
    source.name = target.name;
    source.scale = target.scale;
    source.pivot = target.pivot;
    source.rotation = target.rotation;
    source.alpha = target.alpha;
    source.visible = target.visible;
    source.renderable = target.renderable;
    source.mask = target.mask;
    source.text = target.text;
    source.wordWrapWidth = this._wrapWidth;
    source.stroke = target.stroke;
    source.strokeThickness = target.strokeThickness;
    source.shadowColor = target.shadowColor;
    source.shadowOffsetX = target.shadowOffsetX;
    source.shadowOffsetY = target.shadowOffsetY;
    source.fill = target.fill;
    source.fontFamily = this.fontFamily;
};

/**
 * 解析符号
 * @method _parseSymbol
 * @param {string} parseText 需要解析的文本
 * @param {number} pos 已经解析到的位置
 * @param {array} colorArray
 * @return {object}
 * @private
 */
UIText.prototype._parseSymbol = function(parseText, pos, colorArray) {
    var result = {};
    result.result = false;
    if (parseText[pos] !== "[")
        return result;

    //查找] 或 [
    var i = pos;
    while (++i) {
        // 目前只考虑颜色标示，所以只考虑25的最大长度
        if (parseText[i] === "[" || i >= parseText.length || i - pos > 25) {
            return result;
        }
        if (parseText[i] === "]") {
            break;
        }
    }
    var len = i - pos;
    if (len > 2 && parseText[pos + 1] === "#") {
        // 颜色开始
        result.color = parseText.slice(pos + 1, i);
        result.result = true;
        result.type = UIText._SYMBOL_COLOR;
    }
    else if (len > 6 && parseText[pos + 1] === "r" && parseText[pos + 2] === "g" && parseText[pos + 3] === "b") {
        // 颜色开始
        result.color = parseText.slice(pos + 1, i);
        result.result = true;
        result.type = UIText._SYMBOL_COLOR;
    }
    else if(parseText[pos + 1] === "-" && parseText[pos + 2] === "]") {
        // 颜色结束
        if ((colorArray.length > 0))
            result.result = true;
        result.type = UIText._SYMBOL_END_COLOR;
    }
    // TODO: 添加各种符号

    if (result.result) {
        result.pos = i;
    }

    return result;
};
/**
 * 添加缓存颜色
 * @method _pushColor
 * @param {string} color 颜色
 * @param {number} pos 位置
 * @private
 */
UIText.prototype._pushColor = function(color, pos) {
    var colorItem = {};
    colorItem.color = color;
    colorItem.pos = pos;
    this._colors.push(colorItem);
    this._isTransformDirty = true;
};

/**
 * 设置text颜色
 * @method _setTextColor
 * @private
 */
UIText.prototype._setTextColor = function(phaser) {
    // 清空phaser字体颜色
    if (phaser.clearColors)
        phaser.clearColors();

    if (!phaser.addColor)
        return;

    for (var i = 0; i < this._colors.length; i++) {
        var textColor = this._colors[i].color === 'defaultColor' ? 'defaultColor' : this._colors[i].color;
        phaser.addColor(textColor, this._colors[i].pos);
    }
    this._isTransformDirty = true;
};

/**
 * 解析文本颜色
 * @method _parseTextColor
 * @param {string} text
 * @private
 * @explain 使用范例[#FF0000]变色[-]也可以[#00FF00]嵌[#0000FF]套[-][-]使用
 */
UIText.prototype._parseTextColor = function(text) {
    var showText = "";
    var colorArray = [];
    for (var i = 0; i < text.length; i++) {
        var result = this._parseSymbol(text, i, colorArray);
        if (result.result) {
            // 根据对应的符号处理
            i = result.pos;

            if(result.type === UIText._SYMBOL_COLOR) {
                //添加颜色
                colorArray.push(result.color);
                this._pushColor(result.color, showText.length);
            }
            else if(result.type === UIText._SYMBOL_END_COLOR) {
                colorArray.pop();
                var color = (colorArray.length > 0) ? colorArray[colorArray.length - 1] : "defaultColor";
                this._pushColor(color, showText.length);
            }
        }
        else
            showText += text[i];
    }
    this.textPhaser.text = showText;
    this._isTransformDirty = true;
};

/**
 * 获取需要被序列化的信息描述
 * @overide
 * @internal
 */
UIText.prototype.getMeta = function() {
    var json = Node.prototype.getMeta.call(this);

    // 增加UIText需要序列化的内容
    json.fontFamily = Serializer.NUMBER;
    json.alignH = Serializer.NUMBER;
    json.alignV = Serializer.NUMBER;
    json.font = Serializer.FONT;
    json.bold = Serializer.BOOLEAN;
    json.fontSize = Serializer.NUMBER;
    json.color = Serializer.COLOR;
    json.text = Serializer.STRING;
    json.lineSpacing = Serializer.NUMBER;
    json.wrap = Serializer.BOOLEAN;
    json.enableGlow = Serializer.BOOLEAN;
    json.startColor = Serializer.COLOR;
    json.endColor = Serializer.COLOR;
    json.gradient = Serializer.BOOLEAN;
    json.stroke = Serializer.COLOR;
    json.strokeThickness = Serializer.NUMBER;
    json.glowColor = Serializer.COLOR;
    json.autoSize = Serializer.BOOLEAN;
    json.overflow = Serializer.BOOLEAN;
    json.shadowColor = Serializer.COLOR;
    json.shadowBlur = Serializer.NUMBER;
    json.shadowOffsetX = Serializer.NUMBER;
    json.shadowOffsetY = Serializer.NUMBER;
    json.enableShadow = Serializer.BOOLEAN;

    return json;
};

/**
 * 纠正text坐标
 * @method _adjustTextPos
 * @param {number} width - text宽度
 * @param {number} height - text高度
 * @private
 */
UIText.prototype._adjustTextPos = function() {
    var width = this.textPhaser.realWidth;
    var height = this.textPhaser.realHeight;
    // 纠正水平方向
    this.textPhaser.x = -this.pivotX * this.width;
    if (this._alignH === UIText.RIGHT) {
        this.textPhaser.x = this.textPhaser.x + this.width - width;
    }
    else if (this._alignH === UIText.CENTER) {
        this.textPhaser.x = this.textPhaser.x + (this.width - width) / 2;
    }

    // 纠正垂直方向
    this.textPhaser.y = -this.pivotY * this.height;
    if (this._alignV === UIText.BOTTOM) {
        this.textPhaser.y = this.textPhaser.y + this.height - height;
    }
    else if (this._alignV === UIText.MIDDLE) {
        this.textPhaser.y = this.textPhaser.y + (this.height - height) / 2;
    }
    if (this._enableGlow) {
        this._glowText.x = this.textPhaser.x;
        this._glowText.y = this.textPhaser.y;
        this._glowText.dirty = true;
    }
    this._isTransformDirty = true;
};

/**
 * 字体加载成功回调
 * @param {string} fontName - 字体名称
 * @internal
 */
UIText.prototype._refreshWebFont = function(fontName) {
    if (!/^(?:inherit|serif|sans-serif|cursive|fantasy|monospace)$/.exec(fontName) && !/['",]/.exec(fontName))
    {
        fontName = "'" + fontName + "'";
    }
    if (fontName === this.textPhaser.font) {
        this.textPhaser.dirty = true;
        if (this._glowText) {
            this._glowText.dirty = true;
        }
        this._isTransformDirty = true;
    }
};

/**
 * hack text 的 updateText 方法为了实现文字渐变等功能
 * @hackpp
 */
UIText._phaserUpdateText = function() {
    // 世界缩放比例
    var _qc = this._nqc;
    var canvas = this.canvas;
    var context = this.context;

    var worldScale = _qc.getWorldScale();

    // 取当前 canvas 缩放最大值的参考值（避免手机超标）
    // PC 上容忍限度高一些，直接使用 1920，手机上最小取 960，如果机器分辨率允许使用更高
    var isDesktop = _qc.game.device.desktop;
    var gameWorldWidth = isDesktop ?  1920 : Math.max(960,_qc.game.world.width);
    var gameWorldHeight = isDesktop ?  1920 : Math.max(960, _qc.game.world.height);

    // 判断是否为Android下UC浏览器，Android下的UC绘制不了渐进色文字，
    // 但目前Android下的UC浏览器的UA信息居然都是iPhone无法识别是否为Android
    var UCAndroid = _qc.game.device.phaser.UCBrowser;// && _qc.game.device.phaser.android;

    // 记录下来，后续判断不变化就不再更新
    this._worldScale = new qc.Point(worldScale.x, worldScale.y);

    var canvasScaleX = Math.max(0.2, Math.min(10, Math.abs(worldScale.x)));
    var canvasScaleY = Math.max(0.2, Math.min(10, Math.abs(worldScale.y)));

    var resolution = this.resolution = _qc.game.resolution;
    this.texture.baseTexture.resolution = resolution;
    this.texture.baseTexture.scaleMode = PIXI.scaleModes.NEAREST;

    context.font = this.style.font;

    var outputText = this.text;

    var textWidth = _qc.width;
    if (this.style.wordWrap) {
        outputText = outputText.replace(/\r/ig,"").replace(/\n/ig,"\n");
        var lines = this.runWordWrap(outputText, textWidth);
    }
    else {
        var lines = outputText.split(/(?:\r\n|\r|\n)/);
        for (var i = 0; i < lines.length - 1; i++) {
            lines[i] += '\n';
        }
    }

    // @hackpp
    var gameObject = _qc;

    // calculate text width
    var lineWidths = [];
    var maxLineWidth = 0;

    // hackapp
    var fontHeight = this.determineFontProperties(context.font).fontSize;

    for (var i = 0; i < lines.length; i++) {
        var lineWidth = this._getWorldWidth(lines[i]);
        lineWidths[i] = lineWidth;
        maxLineWidth = Math.max(maxLineWidth, lineWidth);
    }
    maxLineWidth = Math.ceil(maxLineWidth);

    //hackapp
    var strokeThickness = gameObject._strokeThickness;

    var canvasWidth = maxLineWidth +
        Math.max(this.style.stroke && strokeThickness ? strokeThickness * 2 : 0,
                 _qc.enableShadow && this.style.shadowOffsetX ? this.style.shadowOffsetX : 0);
    this.realWidth = canvasWidth;

    // 最大进行限制
    var expectedWidth = canvasWidth * resolution * canvasScaleX;
    if (expectedWidth > gameWorldWidth) {
        canvasScaleX = gameWorldWidth / (canvasWidth * resolution);
        canvasWidth = gameWorldWidth;
    }
    else
        canvasWidth = expectedWidth;

    // 最小保护
    canvas.width = canvasWidth || 1;


    //hackapp
    var lineHeight = fontHeight;
    var height = lineHeight * lines.length;
    var lineSpacing = this._lineSpacing;

    if (lineSpacing < 0 && Math.abs(lineSpacing) > lineHeight)
    {
        lineSpacing = -lineHeight;
    }

    // Adjust for line spacing
    if (lineSpacing !== 0) {
        var diff = lineSpacing * (lines.length - 1);
        height += diff;
    }

    var canvasHeight = _qc.wordHeight = height +
        Math.max(this.style.stroke && strokeThickness ? strokeThickness * 2 : 0,
                 _qc.enableShadow && this.style.shadowOffsetY ? this.style.shadowOffsetY : 0);
    this.realHeight = canvasHeight;

    // 最大进行限制
    var expectedHeight = canvasHeight * resolution * canvasScaleY;
    if (expectedHeight > gameWorldHeight) {
        canvasScaleY = gameWorldHeight / (canvasHeight * resolution);
        canvasHeight = gameWorldHeight;
    }
    else
        canvasHeight = expectedHeight;

    // 最小保护
    canvas.height = canvasHeight || 1;

    gameObject.textRealWidth = maxLineWidth;
    gameObject.textRealHeight = height;

    // 记录下来，绘制的时候需要反向缩放
    this._canvasDownScale = new qc.Point(1 / canvasScaleX, 1 / canvasScaleY);

    context.scale(resolution * canvasScaleX, resolution * canvasScaleY);           //(resolution, resolution);  //

    // 设置水平偏移
    var lineAlignHOffsets = [];
    for (i = 0; i < lines.length; i++) {
        var alignHOffset = 0;

        if (gameObject._alignH === UIText.RIGHT)
        {
            alignHOffset = this.realWidth - lineWidths[i];
        }
        else if (gameObject._alignH === UIText.CENTER)
        {
            alignHOffset = (this.realWidth - lineWidths[i]) / 2;
        }

        lineAlignHOffsets.push(alignHOffset);
    }
    // 设置文字开始打印的Y坐标
    var startYPos = 0;
    if (gameObject._alignV === UIText.BOTTOM) {
        startYPos = this.realHeight - height;
    }
    else if (gameObject._alignV === UIText.MIDDLE) {
        startYPos = (this.realHeight - height) / 2;
    }

    if (navigator.isCocoonJS)
    {
        context.clearRect(0, 0, this.realWidth, this.realHeight);
    }

    context.font = this.style.font;
    context.strokeStyle = this.style.stroke;
    context.textBaseline = "middle";
    if (gameObject.enableShadow) {
        context.shadowOffsetX = this.style.shadowOffsetX;
        context.shadowOffsetY = this.style.shadowOffsetY;
        context.shadowColor = this.style.shadowColor;
        context.shadowBlur = this.style.shadowBlur;
    }
    context.lineCap = 'round';
    context.lineJoin = 'round';
    context.lineWidth = strokeThickness;
    this._charCount = 0;

    var gradientFillStyle;
    if (gameObject._gradient) {
        gradientFillStyle = context.createLinearGradient(0, startYPos, 0, startYPos + height * this.scale.y);
        var offset = 1 / lines.length;
        gradientFillStyle.addColorStop(0, gameObject._startColor);
        for(var i  = 1; i < lines.length; i++) {
            gradientFillStyle.addColorStop(offset * i, gameObject._endColor);
            gradientFillStyle.addColorStop(offset * i, gameObject._startColor);
        }
        gradientFillStyle.addColorStop(1, gameObject._endColor);
        context.fillStyle = gradientFillStyle;
    }
    else {
        context.fillStyle = this.style.fill;
    }

    var vFillStyle = context.fillStyle;
    var vStrokeStyle = context.strokeStyle;
    var linePositionX;
    var linePositionY;

    // Draw text line by line
    for (i = 0; i < lines.length; i++)
    {
        linePositionX = lineAlignHOffsets[i];
        linePositionY = startYPos + i * lineHeight + lineHeight / 2;

        if (i > 0) {
            linePositionY += (lineSpacing * i);
        }

        if (this.colors.length > 0)
        {
            this.updateLine(lines[i], linePositionX, linePositionY, context, strokeThickness, vFillStyle, vStrokeStyle,
                lineHeight, gradientFillStyle, UCAndroid);
        }
        else
        {
            if (this.style.stroke && strokeThickness) {
                context.strokeText(lines[i], linePositionX, linePositionY);
            }
            if (this.style.fill)
            {
                // UC浏览器不支持渐进色文字，在此特殊处置之
                if (gradientFillStyle && UCAndroid) {
                    context.save();
                    context.fillStyle = 'black';
                    context.fillText(lines[i], linePositionX, linePositionY);
                    context.globalCompositeOperation = 'source-in';
                    context.fillStyle = gradientFillStyle;
                    context.beginPath();
                    context.rect(linePositionX, linePositionY - lineHeight/2, lineWidths[i], lineHeight);
                    context.clip();
                    context.fillRect(linePositionX, linePositionY - lineHeight/2, lineWidths[i], lineHeight);
                    context.restore();
                    if (qc.__IS_ANDROID_UC) {
                        var tempCanvas = qc._tempCanvas;
                        if (!tempCanvas) {
                            tempCanvas = qc._tempCanvas = document.createElement('canvas');
                            tempCanvas.width = 128;
                            tempCanvas.height = 128;
                        }
                        context.drawImage(tempCanvas, 0, 0, 2, 2, -5, -5, 1, 1);
                    }
                }
                else {
                    context.fillText(lines[i], linePositionX, linePositionY);
                }
            }
        }
    }

    this.updateTexture();

    /**
     * @hackpp 修复Canvas模式下染色不更新的问题
     * https://github.com/photonstorm/phaser/commit/9362a2b1f480ef570c2a5a05e2fceec03e169262
     * Text with tints applied wouldn't update properly in Canvas mode.
     */
    this.cachedTint = null;

    // TODO 先全部通知
    //_qc._onPhaseTextSizeChange.dispatch(width, height);
    _qc._adjustTextPos();
};


/**
 * hack text 的 determineFontProperties 方法为了获取行高
 * @hackpp
 */
UIText._phaserDetermineFontProperties = function(fontStyle) {
    var properties = PIXI.Text.fontPropertiesCache[fontStyle];

    if (!properties)
    {
        properties = {};

        var canvas = PIXI.Text.fontPropertiesCanvas;
        var context = PIXI.Text.fontPropertiesContext;

        context.font = fontStyle;

        var width = Math.ceil(context.measureText('|MÉq').width);
        if (width === 0) width = 1;
        var baseline = Math.ceil(context.measureText('M').width);

        var height = 2 * baseline;
        if (height === 0) height = 1;

        baseline = baseline * 1.4 | 0;

        canvas.width = width;
        canvas.height = height;

        context.fillStyle = '#f00';
        context.fillRect(0, 0, width, height);

        context.font = fontStyle;

        context.textBaseline = 'alphabetic';
        context.fillStyle = '#000';
        context.fillText('|MÉq', 0, baseline);

        var imagedata = context.getImageData(0, 0, width, height).data;
        var pixels = imagedata.length;
        var line = width * 4;

        var i, j;

        var idx = 0;
        var stop = false;

        // ascent. scan from top to bottom until we find a non red pixel
        for (i = 0; i < baseline; i++)
        {
            for (j = 0; j < line; j += 4)
            {
                if (imagedata[idx + j] !== 255)
                {
                    stop = true;
                    break;
                }
            }
            if (!stop)
            {
                idx += line;
            }
            else
            {
                break;
            }
        }

        properties.ascent = baseline - i;

        idx = pixels - line;
        stop = false;

        // descent. scan from bottom to top until we find a non red pixel
        for (i = height; i > baseline; i--)
        {
            for (j = 0; j < line; j += 4)
            {
                if (imagedata[idx + j] !== 255)
                {
                    stop = true;
                    break;
                }
            }
            if (!stop)
            {
                idx -= line;
            }
            else
            {
                break;
            }
        }

        properties.descent = i - baseline;

        properties.fontSize = properties.ascent + properties.descent;

        PIXI.Text.fontPropertiesCache[fontStyle] = properties;
    }

    return properties;
};


/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 界面按钮对象
 *
 * @class qc.Button
 * @param {qc.Game} game
 * @constructor
 * @internal
 */
var Button = qc.Button = function(game, parent, uuid) {
    qc.UIImage.call(this, game, parent, uuid);

    // 初始化默认的名字
    this.name = 'Button';

    /**
     * @property {qc.Signal} onStateChange - 状态发生变化的事件
     */
    this.onStateChange = new qc.Signal();

    var restore = uuid !== undefined;
    if (restore !== true) {
        /**
         * @property {qc.Text} text - 挂载在按钮上的文本组件
         * @readonly
         */
        this.text = game.add.text(this);
        this.text.text = 'Button';
        this.text.name = 'Text';

        // 设置文本不可交互
        this.text.interactive = false;

        // 设置我可以交互
        this.interactive = true;

        // 我的初始状态为默认状态
        this.state = qc.UIState.NORMAL;

        // 挂载交互效果脚本
        var behaviour = this.addScript('qc.TransitionBehaviour');
        behaviour.target = this;

        // 大小应该等于“我”的大小，位置居中
        this.width = 120;
        this.height = 40;
        this.text.setAnchor(new qc.Point(0, 0), new qc.Point(1, 1));
        this.text.left = 0;
        this.text.right = 0;
        this.text.top = 0;
        this.text.bottom = 0;
        this.text.alignH = UIText.CENTER;
    }

    // 关注按钮按下和松开的回调，切换按钮状态
    var self = this;
    this.onDown.add(function() {
        if (self.state !== qc.UIState.DISABLED) {
            self.state = qc.UIState.PRESSED;
        }
    });
    this.onUp.add(function() {
        if (self.state === qc.UIState.PRESSED) {
            self.state = qc.UIState.NORMAL;
        }
    });

    this._onNativeElementClick = this._processNativeClick.bind(this);
};
Button.prototype = Object.create(qc.UIImage.prototype);
Button.prototype.constructor = Button;

Object.defineProperties(Button.prototype, {
    /**
     * @property {number} state - 按钮的状态
     */
    state : {
        get : function()  { return this._state || qc.UIState.NORMAL; },
        set : function(v) {
            if (this.state === v) return;
            this._state = v;
            this.onStateChange.dispatch();
        }
    },

    /**
     * @property {number} colorTint - 设置按钮的混合色
     * @override qc.UIImage#colorTint
     */
    colorTint : {
        get : function() { return Object.getOwnPropertyDescriptor(qc.Node.prototype, 'colorTint').get.call(this); },
        set : function(v) {
            Object.getOwnPropertyDescriptor(qc.Node.prototype, 'colorTint').set.call(this, v);
            if (this.text)
                this.text.colorTint = v;
        }
    },

    /**
     * @property {string} texture - 按钮的贴图
     * @override qc.UIImage#frame
     */
    frame : {
        get : function() { return Object.getOwnPropertyDescriptor(qc.UIImage.prototype, 'frame').get.call(this); },
        set : function(v) {
            Object.getOwnPropertyDescriptor(qc.UIImage.prototype, 'frame').set.call(this, v);
        }
    },

    /**
     * @property {boolean} nativeEvent - 是否响应浏览器直接事件
     */
    supportNativeEvent : {
        get : function() { return this._supportNativeEvent; },
        set : function(v) {
            if (v === this._supportNativeEvent) {
                return;
            }
            this._supportNativeEvent = v;
            this._checkNativeElement();
        }
    },

    /**
     * @property {Phaser.Signal} onNativeClick - 获取浏览器直接点击事件派发器
     */
    'onNativeClick': {
        get: function() {
            if (!this._onNativeClick) {
                this._onNativeClick = new Phaser.Signal();
            }
            return this._onNativeClick;
        }
    },

    /**
     * @property {string} class - 类名
     * @readonly
     * @internal
     */
    class : {
        get : function() { return 'qc.Button'; }
    }
});

/**
 * 需要序列化的字段和类型
 * @internal
 */
Button.prototype.getMeta = function() {
    var s = qc.Serializer;
    var json = qc.UIImage.prototype.getMeta.call(this);

    // 增加Button需要序列化的内容
    json.text = s.NODE;
    json.state = s.NUMBER;
    json.supportNativeEvent = s.BOOLEAN;
    return json;
};

/**
 * 更新
 * @param  {boolean} force - 是否强制更新NativeElement位置
 */
Button.prototype.update = function(force) {
    if (this.phaser.visible && this.supportNativeEvent) {
        this._checkNativeElement();
        this._updateNativeElement(force);
    }
    else {
        this._removeNativeElement();
    }
};

/**
 * 销毁时删除 Native div 控件
 */
Button.prototype.onDestroy = function() {
    this._removeNativeElement();
    // 调用父亲节点的onDestroy
    qc.UIImage.prototype.onDestroy.call(this);
};

/**
 * 检测当前
 * @private
 */
Button.prototype._checkNativeElement = function() {
    if (this.supportNativeEvent && this.worldVisible && !this._nativeElement) {
        this._createNativeElement();
    }
    else if (this._nativeElement && (!this.supportNativeEvent || !this.worldVisible)) {
        this._removeNativeElement();
    }
};

/**
 * 增加 native 组件
 * @private
 */
Button.prototype._createNativeElement = function() {
    var self = this;
    if (!self._nativeElement) {
        var element = self._nativeElement = document.createElement('div');
        element.addEventListener('click', self._onNativeElementClick, false);
        element.addEventListener('touchstart', self._onNativeElementClick, false);
        var style = element.style;
        style.position = 'absolute';
        style.padding = 0;
        style.margin = 0;
        style.border = 0;
        style.outline = 0;
        style.background = 'none';
        self.game.world.frontDomRoot.appendChild(element);
        self._updateNativeElement();
    }
};

/**
 * 删除 native 组件
 * @private
 */
Button.prototype._removeNativeElement = function() {
    var element = this._nativeElement;
    if (element) {
        this._nativeElement = null;
        element.removeEventListener('click', this._onNativeElementClick, false);
        element.removeEventListener('touchstart', this._onNativeElementClick, false);
        qc.Util.removeHTML(element);
    }
};

/**
 * 更新 native 组件位置
 * @private
 */
Button.prototype._updateNativeElement = function(force) {
    var self = this,
        element = self._nativeElement;
    if (!element) {
        return;
    }
    var now = self.game.time.now;
    // 避免更新太频繁
    if (!force && element._lastUpdate && now - element._lastUpdate < 300) {
        return;
    }
    element._lastUpdate = now;
    qc.Util.updateTransform(self, element)
};

/**
 * 派发 Native 点击事件
 * @param event
 * @private
 */
Button.prototype._processNativeClick = function(event) {
    if (!this.worldVisible) {
        this._checkNativeElement();
    }
    if (this._onNativeClick && this._onNativeClick.getNumListeners() > 0) {
        this._onNativeClick.dispatch(this, event);
    }
};
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 开关对象
 *
 * @class qc.Toggle
 * @param {qc.Game} game
 * @constructor
 * @internal
 */
var Toggle = qc.Toggle = function(game, parent, uuid) {
    qc.Node.call(this, new Phaser.Group(game.phaser, null), parent, uuid);

    // 初始化默认的名字
    this.name = 'Toggle';
    this.interactive = true;

    /**
     * @property {qc.Signal} onStateChange - 状态发生变化的事件
     */
    this.onStateChange = new qc.Signal();

    /**
     * @property {qc.Signal} onValueChange - 开关选中状态变化产生的事件
     */
    this.onValueChange = new qc.Signal();

    var restore = uuid !== undefined;
    if (restore !== true) {
        /**
         * @property {qc.UIImage} background - 背景图片
         * @readonly
         */
        this.background = game.add.image(this);
        this.background.name = 'Background';
        this.background.interactive = false;
        this.background.width = 80;
        this.background.height = 80;

        /**
         * @property {qc.UIImange} checkMark - 选中标记图片
         * @readonly
         */
        this.checkMark = game.add.image(this.background);
        this.checkMark.name = 'CheckMark';
        this.checkMark.interactive = false;

        /**
         * @property {qc.Text} text - 挂载在开关上的文本组件
         * @readonly
         */
        this.text = game.add.text(this);
        this.text.text = 'Button';
        this.text.name = 'Text';
        this.text.interactive = false;

        // 我的初始状态为默认状态
        this.on = false;
        this.state = qc.UIState.NORMAL;

        // 大小应该等于“我”的大小，位置居中
        this.checkMark.pivotX = 0.5;
        this.checkMark.pivotY = 0.5;
        this.checkMark.setAnchor(new qc.Point(0.5, 0.5), new qc.Point(0.5, 0.5));
        this.checkMark.anchoredX = 0;
        this.checkMark.anchoredY = 0;
        this.checkMark.width = 50;
        this.checkMark.height = 50;
        this.text.x = this.background.width + 5;
        this.text.height = this.background.height;

        // 挂载交互效果脚本
        var behaviour = this.addScript('qc.TransitionBehaviour');
        behaviour.target = this.background;
        behaviour.transition = qc.Transition.TEXTURE_SWAP;
    }

    // 关注按钮按下和松开的回调，切换按钮状态
    var self = this;
    this.onDown.add(function() {
        if (self.state !== qc.UIState.DISABLED)
            self.state = qc.UIState.PRESSED;
    });
    this.onUp.add(function() {
        if (self.state === qc.UIState.PRESSED)
            self.state = qc.UIState.NORMAL;
    });

    // 点击时切换开关状态
    this.onClick.add(function() {
        self.on = !self.on;
        self.checkMark.visible = self.on;
    });
};
Toggle.prototype = Object.create(qc.Node.prototype);
Toggle.prototype.constructor = Toggle;

Object.defineProperties(Toggle.prototype, {
    /**
     * @property {boolean} on - 开关的开启状态
     */
    on : {
        get : function()  { return !!this._on; },
        set : function(v) {
            var old = this.on;
            this._on = v;
            this.checkMark.visible = v;
            if (old !== v)
                this.onValueChange.dispatch(this);
        }
    },

    /**
     * @property {number} state - 开关的状态
     */
    state : {
        get : function()  { return this._state || qc.UIState.NORMAL; },
        set : function(v) {
            if (this.state === v) return;
            this._state = v;
            this.onStateChange.dispatch();
        }
    },

    /**
     * @property {string} class - 类名字
     * @readonly
     * @internal
     */
    class : {
        get : function() { return 'qc.Toggle' }
    }
});

/**
 * 需要序列化的字段和类型
 * 部分有依赖关系的字段需要特殊处理
 * @internal
 */
Toggle.prototype.getMeta = function() {
    var s = qc.Serializer;
    var json = qc.Node.prototype.getMeta.call(this);

    // 增加Button需要序列化的内容
    json.background = s.NODE;
    json.checkMark = s.NODE;
    json.text = s.NODE;
    json.on = _CUSTOM_FIELD('on', s.BOOLEAN);
    json.state = _CUSTOM_FIELD('state', s.NUMBER);
    return json;
}

/**
 * @author luohj
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 声音
 *
 * @class qc.Sound
 * @extends qc.Node
 * @param {qc.Game} game - A reference to the currently running game.
 * @constructor
 * @internal
 */
var Sound = qc.Sound = function(game, parent, uuid) {
    this.soundPhaser = new Phaser.Sound(game.phaser);
    this.soundPhaser._qc = this;

    this._loop = false;
    this.game = game;

    // 调用基类的初始
    qc.Node.call(this, new Phaser.Group(this.game.phaser, null), parent, uuid);

    // 初始化默认的名字
    this.name = 'Sound';

    // 注册信号回调
    this._related();

    // 将此声音添加到声音管理器中
    game.phaser.sound._sounds.push(this.soundPhaser);

    // 是否在awake里播放
    this._playOnAwake = false;

    // 默认在声音播放完毕后销毁
    this.destroyWhenStop = true;
};

Sound.prototype = Object.create(qc.Node.prototype);
Sound.prototype.constructor = Sound;

Object.defineProperties(Sound.prototype, {
    /**
     * @property {boolean} loop - 是否循环播放当前声音
     */
    'loop' : {
        get : function() { return this.soundPhaser.loop; },
        set : function(v) {
            this._loop = v;
            this.soundPhaser.loop = v;
            if (this.soundPhaser._sound) {
                this.soundPhaser._sound.loop = v;
            }
        }
    },

    /**
     * @property {SoundAsset} audio
     */
    'audio' : {
        get : function() {
            return this.game.assets.find(this.soundPhaser.key);
        },
        set : function(v) {
            var soundPhaser = this.soundPhaser;
            if (!v) {
                soundPhaser.key = v;
                return;
            }

            // 重新设置声音
            var key = v.key;
            if (soundPhaser && soundPhaser.key) {
                // 保存老值
                var oldLoop = this.loop,
                    oldMute = this.mute,
                    oldVolume = this.volume;

                soundPhaser.destroy();
                soundPhaser = this.soundPhaser = new Phaser.Sound(this.game.phaser, key);
                soundPhaser._qc = this;
                this.game.phaser.sound._sounds.push(soundPhaser);

                // 重置老值
                this.loop = oldLoop;
                this.mute = oldMute;
                this.volume = oldVolume;
            }
            else {
                soundPhaser.key = key;
                if (soundPhaser.usingAudioTag) {
                    if (soundPhaser.game.cache.getSound(key) && soundPhaser.game.cache.isSoundReady(key)) {
                        soundPhaser._sound = soundPhaser.game.cache.getSoundData(key);
                        soundPhaser.totalDuration = 0;

                        if (soundPhaser._sound.duration) {
                            soundPhaser.totalDuration = soundPhaser._sound.duration;
                        }
                    }
                    else {
                        soundPhaser.game.cache.onSoundUnlock.add(soundPhaser.soundHasUnlocked, soundPhaser);
                    }
                }
            }
        }
    },

    /**
     * @property {number} volume - 音量
     */
    'volume' : {
        get : function() { return this.soundPhaser.volume; },
        set : function(v) {
            this.soundPhaser.volume = v;
        }
    },

    /**
     * @property {boolean} playOnAwake - 是否在awake中播放
     */
    'playOnAwake' : {
        get : function() { return this._playOnAwake; },
        set : function(v) { this._playOnAwake = v; }
    },

    /**
    * @property {boolean} - 静音
    */
    'mute' : {
        get : function() { return this.soundPhaser.mute; },
        set : function(v) {this.soundPhaser.mute = v; }
    },

    /**
     * @property {boolean} destroyWhenStop - 声音播放完毕后自动销毁
     */
    destroyWhenStop : {
        get : function() { return this._destroyWhenStop || false; },
        set : function(v) {
            this._destroyWhenStop = v;
        }
    },

    /**
     * @property {string} class - 类的名字
     * @internal
     */
    class : {
        get : function() { return 'qc.Sound'; }
    },
    
    /**
     * @property {boolean} isPlaying - 是否正在播放
     */
    'isPlaying' : {
        get : function() {
            return this.soundPhaser.isPlaying;
        }
    },

    /**
     * @property {boolean} isPaused - 是否暂停中
     */
    'isPaused' : {
        get : function() {
            return this.soundPhaser.paused;
        }
    }
});

/**
 * 序列化完成后派发awake事件
 * @private
 * @override
 */
Sound.prototype._dispatchAwake = function() {
    var self = this;
    Node.prototype._dispatchAwake.call(self);
    if (!self.game.device.editor && self.playOnAwake) {
        // 直接播放声音
        self.play();
    }
};

/**
 * 注册信号回调
 * @method qc.SoundManager#_related
 */
Sound.prototype._related = function() {
    var self = this;
    self.onDecoded = self.soundPhaser.onDecoded;

    /**
     * @property {qc.Signal} onStop
     */
    self.onStop = self.soundPhaser.onStop;
    this.onStop.add(function(sound) {
        if (!self.game.device.editor && self.destroyWhenStop) {
            self.destroy();
        }
    }, self);

    /**
     * @property {qc.Signal} onLoop
     */
    self.onLoop = self.soundPhaser.onLoop;
};
/**
 * 播放声音
 * @method qc.SoundManager#play
 * @return {qc.Sound} The new sound instance.
 */
Sound.prototype.play = function() {
    if (this.isPaused) {
        this.soundPhaser.resume();
        return this;
    }
    // 需要将onended设置为空，否则如果连续调用play的情况下onended会将isPlaying参数错误设置为false
    if (this.soundPhaser.isPlaying && this.soundPhaser._sound && this.soundPhaser.usingWebAudio) {
        this.soundPhaser._sound.onended = null;
    }    
    var markerName = this.marker ? this.marker.name : null;
    this.soundPhaser.play(markerName);
    return this;
};



/**
 * 淡入播放声音
 * @method qc.Sound#fadeIn
 * @param {number} duration - 淡入的时间，单位为毫秒
 */
Sound.prototype.fadeIn = function(duration) {
    this.soundPhaser.fadeIn(duration);
};

/**
 * 暂停声音
 * @method qc.Sound#pause
 */
Sound.prototype.pause = function() { this.soundPhaser.pause(); };

/**
 * 恢复声音
 * @method qc.Sound#resume
 */
Sound.prototype.resume = function() { this.soundPhaser.resume(); };

/**
 * 停止播放声音
 * @method qc.Sound#stop
 */
Sound.prototype.stop = function() {
    // 需要将onended设置为空，否则如果调用stop再play的情况下onended会将isPlaying参数错误设置为false
    if (this.soundPhaser.isPlaying && this.soundPhaser._sound && this.soundPhaser.usingWebAudio) {
        this.soundPhaser._sound.onended = null;
    }
    this.soundPhaser.stop(); 
};

/**
 * 添加播放标记 从指定位置播放
 * @method qc.Sound#addMarker
 * @param {number} start - The start point of this marker in the audio file, given in seconds. 2.5 = 2500ms, 0.5 = 500ms, etc.
 * @param {number} duration - The duration of the marker in seconds. 2.5 = 2500ms, 0.5 = 500ms, etc.
 */
Sound.prototype.addMarker = function(start, duration) {
    // 先移除播放标记
    this.removeMarker();

    // 创建新的播放标记
    this.marker = {};
    this.marker.name = "s";
    this.marker.start = start;
    this.marker.duration = duration;

    // 将标记添加都phaser里
    this.soundPhaser.addMarker(this.marker.name, start, duration);
};

/**
 * 移除播放标记
 * @method qc.Sound#removeMarker
 */
Sound.prototype.removeMarker = function() {
    if (!this.marker) {
        return;
    }

    // 将phaser的标记清空
    this.soundPhaser.currentMarker = "";
    this.soundPhaser.markers = {};
    this.marker = null;
};

/**
 * @method onDestroy
 * @overide
 * @internal
 */
Sound.prototype.onDestroy = function() {
    this.game.phaser.sound.remove(this.soundPhaser);
    // 调用父类的析构
    qc.Node.prototype.onDestroy.call(this);
};

/**
 * 获取需要被序列化的信息描述
 * @overide
 * @internal
 */
Sound.prototype.getMeta = function() {
    var s = qc.Serializer;
    var json = qc.Node.prototype.getMeta.call(this);

    // 增加Sound需要序列化的内容
    json.audio = s.AUDIO;
    json.loop = s.BOOLEAN;
    json.volume = s.NUMBER;
    json.playOnAwake = s.BOOLEAN;
    json.mute = s.BOOLEAN;
    json.destroyWhenStop = s.BOOLEAN;
    return json;
};

/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 滚动视图
 * 用于在一个指定的小区域内显示较大的内容
 * @class qc.ScrollView
 * @param {qc.Game} game - 游戏对象
 * @param {qc.Node | null} parent - 父亲节点
 * @constructor
 */
var ScrollView = qc.ScrollView = function(game, parent, uuid) {
    qc.UIImage.call(this, game, parent, uuid);

    // 初始化默认的名字
    this.name = 'ScrollView';

    /**
     * @property {boolean} canHorizontal - 是否响应水平滑动
     */
    this.canHorizontal = true;

    /**
     * @property {boolean} canVertical - 是否响应竖直滑动
     */
    this.canVertical = true;

    /**
     * @property {number} movementType - 边界限制类型
     */
    this.movementType = ScrollView.MOVEMENT_ELASTIC;

    /**
     * @property {number} elasticity - 当movementType === ScrollView.MOVEMENT_ELASTIC时生效，表示复位速度
     */
    this.elasticity = 1;

    /**
     * @property {boolean} inertia - 是否惯性滑动
     */
    this.inertia = true;

    /**
     * @property {number} decelerationRate - 惯性滑动的减速参数
     */
    this.decelerationRate = 0.03;

    /**
     * @property {number} scrollSensitivity - 响应滚动时的倍率
     */
    this.scrollSensitivity = 1;

    /**
     * @property {boolean} propagationScroll - 是否向上传递滚动事件
     * @type {boolean}
     */
    this.propagationScroll = false;

    /**
     * @property {Phaser.Signal} onValueChange - 偏移值发生变化时调用
     */
    this.onValueChange = new Phaser.Signal();

    /**
     * @property {qc.Rectangle | null} _contentRect - 内容区域在本节点坐标系下的位置
     * @private
     */
    this._contentRect = null;

    /**
     * @property {qc.Rectangle | null} _viewRect - 记录本视窗的大小
     * @private
     */
    this._viewRect = null;

    /**
     * @property {qc.Point | null} _preContentPosition - 上一次处理的显示内容的偏移值
     * @private
     */
    this._preContentPosition = null;

    /**
     * @property {qc.Rectangle | null} _preContentRect - 上一次处理的内容区域在本节点坐标系下的位置
     * @private
     */
    this._preContentRect = null;

    /**
     * @property {qc.Rectangle | null} _preViewRect - 上一次处理的本视窗的大小
     * @private
     */
    this._preViewRect = null;

    /**
     * @property {qc.Point] _velocity - 滚动的速率，每秒移动的距离
     * @private
     */
    this._velocity = new qc.Point(0, 0);

    /**
     * @property {boolean} _isDragging - 是否正在拖拽中
     * @private
     */
    this._isDragging = false;

    // 监听滚动事件和拖拽事件
    this.onWheel.add(this._doWheel, this);
    this.onDragStart.add(this._doDragStart, this);
    this.onDrag.add(this._doDrag, this);
    this.onDragEnd.add(this._doDragEnd, this);
};
ScrollView.prototype = Object.create(qc.UIImage.prototype);
ScrollView.prototype.constructor = ScrollView;

Object.defineProperties(ScrollView.prototype, {

    /**
     * @property {string} class - 类名
     * @
     * @internalreadonly
     */
    class : {
        get : function() { return 'qc.ScrollView'; }
    },

    /**
     * @property {qc.Node} content - 需要滚动显示的内容
     */
    content : {
        get : function() {
            if (this._content && this._content._destroy) {
                this._content = null;
            }
            return this._content;
        },
        set : function(value) {
            this._content = value;
            this._updateBounds();
        }
    },

    /**
     * @property {qc.Node | null} horizontalScrollBar - 水平滚动条
     */
    horizontalScrollBar : {
        get : function() {
            if (this._horizontalScrollBar && this._horizontalScrollBar._destroy) {
                this._horizontalScrollBar = null;
            }
            return this._horizontalScrollBar;
        },
        set : function(value) {
            if (this._horizontalScrollBar) {
                this._horizontalScrollBar.onValueChange.remove(this._setHorizontalNormalizedPosition, this);
            }
            this._horizontalScrollBar = value;
            if (this._horizontalScrollBar) {
                this._horizontalScrollBar.onValueChange.add(this._setHorizontalNormalizedPosition, this);
            }
        }
    },

    /**
     * @property {qc.Node | null} verticalScrollBar - 竖直滚动条
     */
    verticalScrollBar : {
        get : function() {
            if (this._verticalScrollBar && this._verticalScrollBar._destroy) {
                this._verticalScrollBar = null;
            }
            return this._verticalScrollBar;
        },
        set : function(value) {
            if (this._verticalScrollBar) {
                this._verticalScrollBar.onValueChange.remove(this._setVerticalNormalizedPosition, this);
            }
            this._verticalScrollBar = value;
            if (this._verticalScrollBar) {
                this._verticalScrollBar.onValueChange.add(this._setVerticalNormalizedPosition, this);
            }
        }
    },

    /**
     * @property {number} horizontalNormalizedPosition - 水平方向上滚动的比例
     */
    horizontalNormalizedPosition : {
        get : function() {
            this._updateBounds();
            if (this._contentRect.width <= this._viewRect.width) {
                return (this._viewRect.x > this._contentRect.x) ? 1 : 0;
            }
            return (this._viewRect.x - this._contentRect.x) / (this._contentRect.width - this._viewRect.width);
        },
        set : function(value) {
            this.setNormalizedPosition(value, 0);
        }
    },

    /**
     * @property {number} verticalNormalizedPosition - 竖直方向上滚动的比例
     */
    verticalNormalizedPosition : {
        get : function() {
            this._updateBounds();
            if (this._contentRect.height <= this._viewRect.height) {
                return (this._viewRect.y > this._contentRect.y) ? 1 : 0;
            }
            return (this._viewRect.y - this._contentRect.y) / (this._contentRect.height - this._viewRect.height);
        },
        set : function(value) {
            this.setNormalizedPosition(value, 1);
        }
    }
});

/**
 * 需要序列化的字段和类型
 * @internal
 */
ScrollView.prototype.getMeta = function() {
    var s = qc.Serializer;
    var json = qc.UIImage.prototype.getMeta.call(this);

    // 增加Button需要序列化的内容
    json.content = s.NODE;
    json.canHorizontal = s.BOOLEAN;
    json.canVertical = s.BOOLEAN;
    json.movementType = s.NUMBER;
    json.elasticity = s.NUMBER;
    json.inertia = s.BOOLEAN;
    json.decelerationRate = s.NUMBER;
    json.scrollSensitivity = s.NUMBER;
    json.horizontalScrollBar = s.NODE;
    json.verticalScrollBar = s.NODE;
    json.propagationScroll = s.BOOLEAN;
    return json;
};

/**
 * 析构
 */
ScrollView.prototype.onDestroy = function() {
    qc.UIImage.prototype.onDestroy.call(this);
    this.content = null;
    this.horizontalScrollBar = null;
    this.verticalScrollBar = null;
    this.phaser.mask = null;
};

/**
 * 更新
 */
ScrollView.prototype.update = function() {
    this._updateBounds();
};

/**
 * 在所有节点更新完毕后调整滚动条位置
 */
ScrollView.prototype.postUpdate = function() {
    this._updateVelocity();
};

/**
 * 设置水平位置
 * @param value {Number}
 * @private
 */
ScrollView.prototype._setHorizontalNormalizedPosition = function(value) {
    this.setNormalizedPosition(value, 0);
};

/**
 * 设置竖直位置
 * @param value {Number}
 * @private
 */
ScrollView.prototype._setVerticalNormalizedPosition = function(value) {
    this.setNormalizedPosition(value, 1);
};

/**
 * 设置内容显示的位置
 * @param x {Number} - x轴坐标
 * @param y {Number} - y轴坐标
 * @private
 */
ScrollView.prototype._setContentPosition = function(x, y) {
    this.content.x = x;
    this.content.y = y;
};

/**
 * 计算移动指定距离后，显示区域对于视窗的越界偏移
 * @param deltaX {Number} - x轴上移动的距离
 * @param deltaY {Number} - y轴上移动的距离
 * @returns {qc.Point}
 */
ScrollView.prototype._calculateOffset = function(deltaX, deltaY) {
    var offset = new qc.Point(0, 0);
    // 无限制的情况下，没有越界处理
    if (this.movementType === ScrollView.MOVEMENT_UNRESTRICTED) {
        return offset;
    }
    var rect = this.rect;
    var contentRect = this._contentRect;
    var min = new qc.Point(contentRect.x, contentRect.y);
    var max = new qc.Point(contentRect.x + contentRect.width, contentRect.y + contentRect.height);
    if (this.canHorizontal) {
        min.x += deltaX;
        max.x += deltaX;
        if (min.x > rect.x) {
            offset.x = rect.x - min.x;
        }
        else if (max.x < rect.x + rect.width) {
            offset.x = rect.x + rect.width - max.x;
        }
    }

    if (this.canVertical) {
        min.y += deltaY;
        max.y += deltaY;
        if (min.y > rect.y) {
            offset.y = rect.y - min.y;
        }
        else if (max.y < rect.y + rect.height) {
            offset.y = rect.y + rect.height - max.y;
        }
    }

    return offset;
};

/**
 * 处理回弹效果
 * @param position {qc.Point} - 当前位置
 * @param offset {qc.Point} - 需要处理的越界值
 * @param deltaTime {Number} - 上一帧到现在的时间
 * @param axisPos {'x' | 'y') - 滚动轴
 * @private
 */
ScrollView.prototype._calcVelocityEffect = function(position, offset, deltaTime, axisPos) {
    // 弹性处理
    if (this.movementType === ScrollView.MOVEMENT_ELASTIC && offset[axisPos] !== 0) {
        var lastOffset = this['_lastOffset_' + axisPos] || 0;
        if (Math.abs(lastOffset) < Math.abs(offset[axisPos])) {
            this['_lastOffset_' + axisPos] = offset[axisPos];
            this._currSmoothetTime = deltaTime;
        }
        else {
            this['_lastOffset_' + axisPos] = offset[axisPos];
            this._currSmoothetTime += deltaTime;
        }
        var smootherTime = this.elasticity <= 0 ? deltaTime : this.elasticity;
        var ret = this.game.math.smoothDamp(position[axisPos], position[axisPos] + offset[axisPos], this._velocity[axisPos], this.elasticity, Number.MAX_VALUE, deltaTime / 100);
        if (Math.abs(position[axisPos] + offset[axisPos] - ret[0]) < 0.0001) {
            position[axisPos] = position[axisPos] + offset[axisPos];
            this._velocity[axisPos] = 0;
        }
        else {
            position[axisPos] = ret[0];
            this._velocity[axisPos] = ret[1];
        }
        //position[axisPos] = position[axisPos] + offset[axisPos] * Phaser.Math.smoothstep(this._currSmoothetTime, 0, smootherTime * 1000);
        //this._velocity[axisPos] = 0;

    }
    else if (this.movementType === ScrollView.MOVEMENT_CLAMPED && offset[axisPos] !== 0) {
        position[axisPos] = position[axisPos] + offset[axisPos];
    }
    else if (this.inertia) {
        // 计算速度衰减
        var velocity = this._velocity[axisPos] * Math.pow(Math.abs(this.decelerationRate), deltaTime / 1000);
        if (Math.abs(velocity) < 1) {
            velocity = 0;
        }
        this._velocity[axisPos] = velocity;
        position[axisPos] = position[axisPos] + velocity * deltaTime / 1000;
    }
    else {
        this._velocity[axisPos] = 0;
    }
};

/**
 * 弹性形变
 * @param overStretching {Number} - 越界值，相当于力的大小
 * @param viewSize {Number} - 正常值
 * @return {Number} 产生的形变值
 * @private
 */
ScrollView.prototype._rubberDelta = function(overStretching, viewSize) {
    return (1 - (1 / ((Math.abs(overStretching) * 0.55 / viewSize) + 1))) * viewSize * this.game.math.sign(overStretching);
};

/**
 * 更新处理速度信息
 * @private
 */
ScrollView.prototype._updateVelocity = function() {
    if (!this.content) {
        return;
    }

    this._updateBounds();
    var deltaTime = this.game.time.deltaTime;
    var offset = this._calculateOffset(0, 0);
    var position = new qc.Point(this.content.x, this.content.y);

    // 拖拽中，或者越界的偏移为0，或者回弹的速度为0时跳过
    if (!this._isDragging &&
        ((offset.x !== 0 || offset.y !== 0) ||
        (this._velocity.x !== 0 || this._velocity.y !== 0))) {

        this._calcVelocityEffect(position, offset, deltaTime, 'x');
        this._calcVelocityEffect(position, offset, deltaTime, 'y');

        if (this._velocity.x !== 0 ||
            this._velocity.y !== 0) {
            if (this.movementType === ScrollView.MOVEMENT_CLAMPED) {
                offset = this._calculateOffset(position.x - this.content.x, position.y - this.content.y);
                position.x += offset.x;
                position.y += offset.y;
            }
        }
        this._setContentPosition(position.x, position.y);
    }

    if (this._isDragging && this.inertia) {
        var vx = this.content.x - this._preContentPosition.x;
        var vy = this.content.y - this._preContentPosition.y;

        var l =  this.game.math.clamp(deltaTime / 1000, 0, 1);

        this._velocity.x = vx / l;
        this._velocity.y = vy / l;
    }

    if (!this._preViewRect || !qc.Rectangle.equals(this._viewRect, this._preViewRect) ||
        !this._preContentRect || !qc.Rectangle.equals(this._contentRect, this._preContentRect) ||
        !this._preContentPosition || !qc.Point.equals(this._preContentPosition, position)) {
        this._updateScrollBars(offset.x, offset.y);
        this.onValueChange.dispatch(new qc.Point(this.horizontalNormalizedPosition, this.verticalNormalizedPosition));
        this._updatePrevData();
    }
};

/**
 * 设置指定方向上的滚动值
 * @param value {number} - 设置的值
 * @param axis {number} - 坐标轴，0：x轴，1：y轴
 */
ScrollView.prototype.setNormalizedPosition = function(value, axis) {
    this._updateBounds();
    if (!this.content || !this._contentRect) {
        return;
    }
    var lenProperty = axis ? 'height' : 'width';
    var posProperty = axis ? 'y' : 'x';
    var hiddenLength = this._contentRect[lenProperty] - this._viewRect[lenProperty];
    var contentMinPosition = this._viewRect[posProperty] - value * hiddenLength;
    var newLocalPosition = this.content[posProperty] + contentMinPosition - this._contentRect[posProperty];
    var localPosition = this.content[posProperty];
    // 滚动位置相差1个像素时开始处理
    if (Math.abs(localPosition - newLocalPosition) > 1) {
        this.content[posProperty] = newLocalPosition;
        // 设置滚动速率为0
        this._velocity[posProperty] = 0;
        this._updateBounds();
    }
};

/**
 * 更新记录的上一次信息
 * @private
 */
ScrollView.prototype._updatePrevData = function() {
    if (this.content) {
        this._preContentPosition = new qc.Point(this.content.x, this.content.y);
    }
    else {
        this._preContentPosition = new qc.Point(0, 0);
    }
    this._preContentRect = this._contentRect;
    this._preViewRect = this._viewRect;
};

/**
 * 更新滚动条的滚动信息
 * @param offX {number} - 在水平方向上的偏移
 * @param offY {number} - 在竖直方向上的偏移
 * @private
 */
ScrollView.prototype._updateScrollBars = function(offX, offY) {
    if (this.horizontalScrollBar) {
        if (this._contentRect.width > 0) {
            var barSize = (this._viewRect.width - Math.abs(offX)) / this._contentRect.width;
            this.horizontalScrollBar.size = Phaser.Math.clamp(barSize,0, 1);
        }
        else {
            this.horizontalScrollBar.size = 1;
        }
        this.horizontalScrollBar.value = this.horizontalNormalizedPosition;
    }

    if (this.verticalScrollBar) {
        if (this._contentRect.height > 0) {
            var barSize = (this._viewRect.height - Math.abs(offY)) / this._contentRect.height;
            this.verticalScrollBar.size = Phaser.Math.clamp(barSize, 0, 1);
        }
        else {
            this.verticalScrollBar.size = 1;
        }
        this.verticalScrollBar.value = this.verticalNormalizedPosition;
    }
};

/**
 * 更新区域信息
 * @private
 */
ScrollView.prototype._updateBounds = function() {
    this._viewRect = this.rect;
    this._updateContentBounds();
    if (!this.content)
        return;
    var viewRect = this.rect;

    // 如果内容区域下于显示区域，则模拟内容区域为显示区域大小
    var diffWidth = viewRect.width - this._contentRect.width;
    var diffHeight = viewRect.height - this._contentRect.height;
    if (diffWidth > 0) {
        this._contentRect.width = viewRect.width;
        this._contentRect.x -= diffWidth * this.content.pivotX;
    }
    if (diffHeight > 0) {
        this._contentRect.height = viewRect.height;
        this._contentRect.y -= diffHeight * this.content.pivotY;
    }
};

/**
 * 更新内容的区域信息
 * @private
 */
ScrollView.prototype._updateContentBounds = function() {
    if (!this.content) {
        this._contentRect = new qc.Rectangle(0, 0, 0, 0);
        return;
    }

    this._contentRect = qc.Bounds.getBox(this.content, qc.Bounds.USE_BOUNDS, false, 1, this);
};

/**
 * 滚动条滚动时
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.WheelEvent} - 拖拽结束事件
 * @private
 */
ScrollView.prototype._doWheel = function(node, event) {
    if (!this.content) {
        return;
    }

    this._updateBounds();

    var delta = new qc.Point(event.source.deltaX, event.source.deltaY);
    if (!this.canVertical) {
        delta.y = 0;
    }
    if (!this.canHorizontal) {
        delta.x = 0;
    }

    var deltaX = delta.x * this.scrollSensitivity;
    var deltaY = delta.y * this.scrollSensitivity;
    this.doScroll(deltaX, deltaY, false);
};

/**
 * 开始拖拽
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.DragStartEvent} - 开始拖拽事件
 * @private
 */
ScrollView.prototype._doDragStart = function(node, event) {
    if (event.source.eventId !== qc.Mouse.BUTTON_LEFT) {
        return;
    }

    this._updateBounds();
    // 记录当前点击时内容的显示位置
    this._contentStartPosition = new qc.Point(this.content.x, this.content.y);
    this._pointerStartCursor = this.toLocal(new qc.Point(event.source.startX, event.source.startY));
    this._isDragging = true;
};

/**
 * 处理拖拽结束
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.DragEndEvent} - 拖拽结束事件
 * @private
 */
ScrollView.prototype._doDragEnd = function(node, event) {
    if (event.source.eventId !== qc.Mouse.BUTTON_LEFT) {
        return;
    }
    this._isDragging = false;
};

/**
 * 处理拖拽事件
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.DragEvent} - 拖拽结束事件
 * @private
 */
ScrollView.prototype._doDrag = function(node, event) {
    if (event.source.eventId !== qc.Mouse.BUTTON_LEFT) {
        return;
    }

    this._updateBounds();
    var cursor = this.toLocal(new qc.Point(event.source.x, event.source.y));
    if (!this._pointerStartCursor)
        return;

    var deltaX = this.canHorizontal ? (cursor.x - this._pointerStartCursor.x) : 0;
    var deltaY = this.canVertical ? (cursor.y - this._pointerStartCursor.y) : 0;
    this.doScroll(this._contentStartPosition.x + deltaX - this.content.x,
        this._contentStartPosition.y + deltaY - this.content.y,
        true);
};

/**
 * 处理滚动事件
 * @param deltaX {number} - x轴偏移
 * @param deltaY {number} - x轴偏移
 * @param isDrag {boolean} - 是否是拖拽
 */
ScrollView.prototype.doScroll = function(deltaX, deltaY, isDrag) {
    var position = new qc.Point(this.content.x, this.content.y);
    position.x += deltaX;
    position.y += deltaY;
    var offset = this._calculateOffset(deltaX, deltaY);
    position.x += offset.x;
    position.y += offset.y;
    if (this.movementType === ScrollView.MOVEMENT_CLAMPED && this.propagationScroll) {
        var parentScroll = this.parent;
        while (!(parentScroll instanceof ScrollView) && parentScroll !== this.game.world) {
            parentScroll = parentScroll.parent;
        }
        if (parentScroll instanceof ScrollView) {
            parentScroll.doScroll(-offset.x, -offset.y, isDrag);
        }
    }
    else if (this.movementType === ScrollView.MOVEMENT_ELASTIC) {
        if (isDrag) {
            if (offset.x !== 0) {
                position.x = position.x - this._rubberDelta(offset.x, this._viewRect.width);
            }
            if (offset.y !== 0) {
                position.y = position.y - this._rubberDelta(offset.y, this._viewRect.height);
            }
        }
        else {
            position.x -= offset.x;
            position.y -= offset.y;
            if (Math.abs(offset.x) > this._viewRect.width) {
                position.x += offset.x - this.game.math.sign(offset.x) * this._viewRect.width;
            }
            if (Math.abs(offset.y) > this._viewRect.height) {
                position.y += offset.y - this.game.math.sign(offset.y) * this._viewRect.height;
            }
        }
    }
    this._setContentPosition(position.x, position.y);
    if (!isDrag) {
        this._updateBounds();
    }
};

/**
 * 滚动区域无限制
 * @constant
 * @type {number}
 */
ScrollView.MOVEMENT_UNRESTRICTED = 0;

/**
 * 滚动区域有限制，但可以超越边界，之后被拖回
 * @constant
 * @type {number}
 */
ScrollView.MOVEMENT_ELASTIC = 1;

/**
 * 滚动区域有限制，无法超过边界
 * @constant
 * @type {number}
 */
ScrollView.MOVEMENT_CLAMPED = 2;

/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 滚动条控件
 * 用于滚动ScrollView视图，和显示当前滚动偏移，该控制主要显示滚动条背景，需要设置滑块
 * @class qc.ScrollBar
 * @param {qc.Game} game - 游戏对象
 * @param {qc.Node | null} parent - 父亲节点
 * @constructor
 */
var ScrollBar = qc.ScrollBar = function(game, parent, uuid) {
    qc.UIImage.call(this, game, parent, uuid);

    /**
     * @property {string} name - 控件名字
     */
    this.name = 'ScrollBar';

    // 设置默认值
    this._setValue(0, false);

    // 监听滚动事件和拖拽事件
    this.onWheel.add(this._doWheel, this);
    this.onDown.add(this._doDown, this);
    this.onUp.add(this._doUp, this);
    this.onDragStart.add(this._doDragStart, this);
    this.onDrag.add(this._doDrag, this);
    this.onDragEnd.add(this._doDragEnd, this);
};
ScrollBar.prototype = Object.create(qc.UIImage.prototype);
ScrollBar.prototype.constructor = ScrollBar;

Object.defineProperties(ScrollBar.prototype, {
    /**
     * @property {string} class - 类名
     * @readonly
     * @internal
     */
    class : {
        get : function() { return 'qc.ScrollBar'; }
    },

    /**
     * @property {qc.Node} sliders - 滑块，滑块需要有一个指定rect的父节点，用来处理滑动
     */
    sliders : {
        get : function() {
            if (this._sliders && this._sliders._destroy) {
                this._sliders = null;
            }
            return this._sliders;
        },
        set : function(value) {
            this._sliders = value;
            this._updateSliders();
        }
    },

    /**
     * @property {Number} direction - 滚动条的滑动方向
     */
    direction : {
        get : function() {
            return this._direction || ScrollBar.LEFT_TO_RIGHT;
        },
        set : function (value) {
            this._setDirection(value, false);
        }
    },

    /**
     * @property {Number} value - 当前滑动的值,[0~1]
     */
    value : {
        get : function() {
            return this._value;
        },
        set : function(value) {
            this._setValue(value, true);
        }
    },

    /**
     * @property {Number} size - 滑块长度和滑块区域的比例,(0,1], 滑块最小5个像素
     */
    size : {
        get : function() {
            return this._size;
        },
        set : function(value) {
            if (!this.fixSlidersSize) {
                this._size = Phaser.Math.clamp(value, 0, 1);
                this._size = Math.max(5 / Math.max(this.height, this.width), this._size);
                this._updateSliders();
            }
        }
    },

    /**
     * @property {Number} numberOfStep - 滚动时，滑块从0到1的步数
     */
    numberOfStep : {
        get : function() { return this._numberOfStep; },
        set : function(value) { this._numberOfStep = value; }
    },

    /**
     * @property {Phaser.Signal} onValueChange - 当滑块值发生变化时的事件
     * @readonly
     */
    onValueChange : {
        get : function() {
            if (!this._onValueChange) {
                this._onValueChange = new Phaser.Signal();
            }
            return this._onValueChange;
        }
    },

    /**
     * @property {Number} stepSize - 滚动时每步改变的值
     */
    stepSize : {
        get : function() {
            return (this.numberOfStep > 1) ? 1.0 / (this.numberOfStep - 1) : 0.1;
        }
    },

    /**
     * @property {boolean} fixSlidersSize - 是否固定滑块大小
     */
    fixSlidersSize : {
        get : function() { return this._fixSlidersSize; },
        set : function(value) { this._fixSlidersSize = value; }
    },

    /**
     * @property {'x'|'y'} _axisPos - 当前滚动条移动方向的位置参数
     * @readonly
     * @private
     */
    _axisPos : {
        get : function() {
            return this.direction === ScrollBar.LEFT_TO_RIGHT || this.direction === ScrollBar.RIGHT_TO_LEFT ?
                'x' : 'y';
        }
    },

    /**
     * @property {'width'|'height'} _axisSize - 当前滚动条移动方向的大小参数
     * @readonly
     * @private
     */
    _axisSize : {
        get : function() {
            return this.direction === ScrollBar.LEFT_TO_RIGHT || this.direction === ScrollBar.RIGHT_TO_LEFT ?
                'width' : 'height';
        }
    },

    /**
     * @property {boolean} reverseValue - 是否是逆向移动
     * @readonly
     * @private
     */
    _reverseValue : {
        get : function() {
            return this.direction === ScrollBar.RIGHT_TO_LEFT || this.direction === ScrollBar.BOTTOM_TO_TOP;
        }
    },

    /**
     * @property {qc.Rectangle} _slidingRect - 滑动区域
     * @readonly
     * @private
     */
    _slidingRect : {
        get : function() {
            if (!this.sliders || !this.sliders.parent)
                return null;
            else
                return this.sliders.parent.rect;
        }
    }
});

/**
 * 获取需要被序列化的信息描述
 * @override
 * @internal
 */
ScrollBar.prototype.getMeta = function() {
    var s = qc.Serializer;
    var json = qc.UIImage.prototype.getMeta.call(this);

    json.sliders = s.NODE;
    json.value = s.NUMBER;
    json.size = s.NUMBER;
    json.numberOfStep = s.NUMBER;
    json.direction = s.NUMBER;
    json.fixSlidersSize = s.BOOLEAN;
    return json;
};

/**
 * 析构
 */
ScrollBar.prototype.onDestroy = function() {
    qc.UIImage.prototype.onDestroy.call(this);
    this.sliders = null;
};

/**
 * 设置滚动条方向
 * @param direction {Number} - 滚动条滚动方向
 * @param reLayout {boolean} - 是否重新布局，当滚动轴线变化时，宽高互换
 * @private
 */
ScrollBar.prototype._setDirection = function(direction, reLayout) {
    if (this._direction === direction)
        return;
    var oldAxis = this._axisPos;
    var oldReverse = this._reverseValue;
    this._direction = direction;

    if (reLayout && oldAxis != this._axisPos) {
        var oldWidth = this.width;
        this.width = this.height;
        this.height = oldWidth;
    }
    this._updateSliders();
};

/**
 * 设置当前的值
 * @param input {number} - 需要设置的值
 * @param sendCallBack {boolean} - 是否需要触发事件
 * @private
 */
ScrollBar.prototype._setValue = function(input, sendCallBack) {
    var currValue = this._value;
    this._value = Phaser.Math.clamp(input, 0, 1);

    if (currValue === this.value) {
        return;
    }

    // 更新滑块
    this._updateSliders();

    if (sendCallBack) {
        this.onValueChange.dispatch(this.value);
    }
};

/**
 * 更新完成后处理
 * @method postUpdate
 * @internal
 */
ScrollBar.prototype.postUpdate = function() {
    if (this._isMoveNotDragging) {
        this._doPursued();
    }
};

/**
 * 更新滑块的位置
 * @private
 */
ScrollBar.prototype._updateSliders = function() {
    if (!this._slidingRect) {
        return;
    }
    var minAnchor = new qc.Point(0, 0);
    var maxAnchor = new qc.Point(1, 1);
    var movement = this.value * (1 - this.size);
    var axisPos = this._axisPos;
    if (this._reverseValue) {
        minAnchor[axisPos] = 1 - movement - this.size;
        maxAnchor[axisPos] = 1 - movement;
    }
    else {
        minAnchor[axisPos] = movement;
        maxAnchor[axisPos] = movement + this.size;
    }
    this.sliders.setAnchor(minAnchor, maxAnchor, false);
};

/**
 * 获得滑块在ScrollBar上映射的边界信息
 * @return {qc.Rectangle}
 * @private
 */
ScrollBar.prototype._getSlidersRectInBar = function() {
    var minAnchor = this.sliders.minAnchor;
    var maxAnchor = this.sliders.maxAnchor;
    var slidingRect = this._slidingRect;

    // 先算出滑块在滑动区域上的虚拟滑块位置
    var l = slidingRect.width * minAnchor.x;
    var t = slidingRect.height * minAnchor.y;
    var w = slidingRect.width * (maxAnchor.x - minAnchor.x);
    var h = slidingRect.height * (maxAnchor.y - minAnchor.y);
    // 等比从滑动区域对应到ScrollBar上
    var zoomW = slidingRect.width / this.rect.width;
    var zoomH = slidingRect.height / this.rect.height;
    return new qc.Rectangle(
        this.rect.x + l * zoomW,
        this.rect.y + t * zoomH,
        w * zoomW,
        h * zoomH
    );
};

/**
 * 获得滑块坐标系中的点在ScrollBar上映射的边界信息
 * @param {qc.Point} point - 滑块坐标系中的点
 * @return {qc.point}
 * @private
 */
ScrollBar.prototype._getSlidersPointInBar = function(point) {
    var minAnchor = this.sliders.minAnchor;
    var maxAnchor = this.sliders.maxAnchor;
    var slidingRect = this._slidingRect;
    // 先算出滑块在滑动区域上的虚拟滑块位置
    var slidingZoomW = this.sliders.rect.width / slidingRect.width;
    var slidingZoomH = this.sliders.rect.height / slidingRect.height;

    var x = slidingRect.width * minAnchor.x + point.x * slidingZoomW;
    var y = slidingRect.height * minAnchor.y + point.y * slidingZoomH;

    // 等比从滑动区域对应到ScrollBar上
    var zoomW = slidingRect.width / this.rect.width;
    var zoomH = slidingRect.height / this.rect.height;
    return new qc.Point(
        this.rect.x + x * zoomW,
        this.rect.y + y * zoomH
    );
};

/**
 * 滑块追赶点击位置
 * @private
 */
ScrollBar.prototype._doPursued = function() {
    var slidersRectInBar = this._getSlidersRectInBar();
    var axisPos = this._axisPos;
    var axisSize = this._axisSize;

    if (this._pursuedPoint >= slidersRectInBar[axisPos] &&
        this._pursuedPoint <= slidersRectInBar[axisPos] + slidersRectInBar[axisSize]) {
        this._isMoveNotDragging = false;
        this._offset = this._pursuedPoint - slidersRectInBar[axisPos] - slidersRectInBar[axisSize] / 2;
        this._pursuedPoint = 0;
        this._pursuedType = 0;
        return;
    }
    if ((this._pursuedPoint < slidersRectInBar[axisPos]) ^ this._reverseValue) {
        if (this._pursuedType !== 2) {
            this.value -= this.stepSize;
            this._pursuedType = 1;
        }
    }
    else {
        if (this._pursuedType !== 1) {
            this.value += this.stepSize;
            this._pursuedType = 2;
        }
    }
};

/**
 * 当按下时
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.PointerEvent) - 点击事件
 * @private
 */
ScrollBar.prototype._doDown = function(node, event) {
    if (!this.sliders || event.source.eventId != qc.Mouse.BUTTON_LEFT) {
        return;
    }
    var globalPoint = new qc.Point(event.source.x, event.source.y);
    if (!this.sliders.rectContains(globalPoint)) {
        var barPoint = this.toLocal(globalPoint);
        this._isMoveNotDragging = true;
        this._pursuedPoint = barPoint[this._axisPos];
        this._pursuedType = 0;
    }
};

/**
 * 当弹起时
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.PointerEvent) - 点击事件
 * @private
 */
ScrollBar.prototype._doUp = function(node, event) {
    this._isMoveNotDragging = false;
};

/**
 * 开始拖拽
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.DragStartEvent} - 开始拖拽事件
 * @private
 */
ScrollBar.prototype._doDragStart = function(node, event) {
    if (!this.sliders || event.source.eventId != qc.Mouse.BUTTON_LEFT) {
        return;
    }
    this._isMoveNotDragging = false;
    var globalPoint = new qc.Point(event.source.x, event.source.y);
    var barPoint = this.toLocal(globalPoint);
    var slidersRectInBar = this._getSlidersRectInBar();
    var axisPos = this._axisPos;
    var axisSize = this._axisSize;

    // 记录当前点击点距离虚拟滑块中心的位置
    this._offset = barPoint[axisPos] - slidersRectInBar[axisPos] - slidersRectInBar[axisSize] / 2;
};

/**
 * 处理拖拽结束
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.DragEndEvent} - 拖拽结束事件
 * @private
 */
ScrollBar.prototype._doDragEnd = function(node, event) {
    this._isMoveNotDragging = false;
};

/**
 * 处理拖拽事件
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.DragEvent} - 拖拽结束事件
 * @private
 */
ScrollBar.prototype._doDrag = function(node, event) {
    if (!this.sliders || event.source.eventId != qc.Mouse.BUTTON_LEFT) {
        return;
    }
    var globalPoint = new qc.Point(event.source.x, event.source.y);
    var barPoint = this.toLocal(globalPoint);
    var slidersRectInBar = this._getSlidersRectInBar();
    var axisPos = this._axisPos;
    var axisSize = this._axisSize;

    // 计算当前状态下虚拟滑块到边界的距离
    var distance = barPoint[axisPos] - this._offset - this.rect[axisPos] - slidersRectInBar[axisSize] / 2;
    var remainingSize = this.rect[axisSize] * (1 - this.size);
    switch(this.direction) {
        case ScrollBar.LEFT_TO_RIGHT:
        case ScrollBar.TOP_TO_BOTTOM:
            this._setValue(distance / remainingSize, true);
            break;
        case ScrollBar.RIGHT_TO_LEFT:
        case ScrollBar.BOTTOM_TO_TOP:
            this._setValue(1 - distance / remainingSize, true);
            break;
    }
};

/**
 * 滚动条滚动时
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.WheelEvent} - 拖拽结束事件
 * @private
 */
ScrollBar.prototype._doWheel = function(node, event) {
    if (!this.sliders) {
        return;
    }

    var movement = event.source['delta' + this._axisPos.toUpperCase()];
    var value = this.value;
    value += (this._reverseValue ? -1 : 1) * Math.sign(movement) * this.stepSize;
    this._setValue(value, true);
};

/**
 * 水平方向滚动，左端为0，右端为1
 * @constant
 * @type {integer}
 */
ScrollBar.LEFT_TO_RIGHT = 0;

/**
 * 水平方向滚动，左端为1，右端为0
 * @constant
 * @type {integer}
 */
ScrollBar.RIGHT_TO_LEFT = 1;

/**
 * 竖直方向滚动，顶端为0，底端为1
 * @constant
 * @type {integer}
 */
ScrollBar.TOP_TO_BOTTOM = 2;

/**
 * 竖直方向滚动，顶端为1，底端为0
 * @constant
 * @type {integer}
 */
ScrollBar.BOTTOM_TO_TOP = 3;
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 进度条控件
 * 用于显示进度，分为确定类型和不确定类型
 * 确定类型：基于值的填充模式，可以确定当前的进度值
 * 不确定类型：重复循环播放
 * 显示类型又分为水平进度条，竖直进度条和圆形进度条，
 * @class qc.ProgressBar
 * @param {qc.Game} game - 游戏对象
 * @param {qc.Node | null} parent - 父亲节点
 * @param {boolean} - 是否是反序列化创建
 * @constructor
 */
var ProgressBar = qc.ProgressBar = function(game, parent, uuid) {
    qc.UIImage.call(this, game, parent, uuid);

    /**
     * @property {string} name - 控件名字
     */
    this.name = 'ProgressBar';

    // 设置默认值
    this.style = ProgressBar.STYLE_HORIZONTAL;
    this.value = 0;
    this.minValue = 0;
    this.maxValue = 1;
    this.indeterminable = false;
    this.loopTime = 3000;
    this.numberOfStep = 0;
    this.fixedSize = -1;
    this._canSlideOut = true;
    this.clipSliders = false;
    this.showMode = ProgressBar.SHOW_PROCESSED;
    this.reverse = false;
};
ProgressBar.prototype = Object.create(qc.UIImage.prototype);
ProgressBar.prototype.constructor = ProgressBar;

Object.defineProperties(ProgressBar.prototype, {
    /**
     * @property {string} class - 类名
     * @readonly
     * @internal
     */
    class : {
        get : function() { return 'qc.ProgressBar'; }
    },

    /**
     * @property {Number} style - 显示样式，STYLE_HORIZONTAL 水平进度条，STYLE_VERTICAL 竖直进度条，STYLE_CIRCLE 圆形进度条
     */
    style : {
        get : function() { return this._style; },
        set : function(value) {
            this._setStyle(value);
        }
    },

    /**
     * @property {qc.Node} sliders - 滑块，用来表示进度
     */
    sliders : {
        get : function() {
            if (this._sliders && this._sliders._destroy) {
                this._sliders = null;
            }
            return this._sliders;
        },
        set : function(value) {
            this._sliders = value;
            this._updateSliders();
        }
    },

    /**
     * @property {boolean} indeterminable - 是否是不确定类型，如果无法明确知道进度或者结束值，则使用不确定类型，进度条将循环播放
     * 对于水平进度条，不确定状态为一截滑块，在进度条中循环水平滑动
     * 对于竖直进度条，不确定状态为一截滑块，在进度条中循环竖直滑动
     * 对于圆形进度条，不确定状态下，滑块区域将进行循环旋转
     */
    indeterminable : {
        get : function() { return this._indeterminable; },
        set : function(value) {
            this._indeterminable = value;
        }
    },

    /**
     * @property {Number} minValue - 当前进度条的最小值
     */
    minValue : {
        get : function() { return (isNaN(this._minValue) || this._minValue === null) ? 0 : this._minValue; },
        set : function(value) { this.setMinMax(value); }
    },

    /**
     * @property {Number} maxValue - 当前进度条的最大值
     */
    maxValue : {
        get : function() { return (isNaN(this._maxValue) || this._maxValue === null) ? 1 : this._maxValue; },
        set : function(value) { this.setMinMax(null, value); }
    },

    /**
     * @property {Number} length - 当前进度条的长度，maxValue - minValue
     */
    length : {
        get : function() { return this.maxValue - this.minValue; }
    },

    /**
     * @property {Number} value - 显示用的进度值，将进度值转化为步进值的倍数
     * @readonly
     * @private
     */
    stepValue : {
        get : function() {
            var stepSize = this.stepSize;
            if (stepSize > 0) {
                return this.minValue + Math.round((this.value - this.minValue) / stepSize) * stepSize;
            }
            else {
                return this.value;
            }
        },
        set : function (value) {
            var stepSize = this.stepSize;
            if (stepSize > 0) {
                this._setValue(this.minValue + Math.round((value - this.minValue) / stepSize) * stepSize, true);
            }
            else {
                this._setValue(value);
            }
        }
    },

    /**
     * @property {Number} value - 当前实际进度值,[minValue, maxValue]
     */
    value : {
        get : function() { return this._value || 0; },
        set : function(value) {
            if (value !== this._value) {
                this._setValue(value, true);
            }
        }
    },

    /**
     * @property {Number} loopTime - 循环时间，仅当indeterminable为true时有效，单位毫秒
     */
    loopTime : {
        get : function() { return this._loopTime; },
        set : function(value) { this._loopTime = value; }
    },

    /**
     * @property {Number} numberOfStep - 在循环播放时，从0到max的步数，仅当indeterminable为true时有效, 小于等于0时，按实际值显示
     */
    numberOfStep : {
        get : function() { return this._numberOfStep; },
        set : function(value) { this._numberOfStep = value; }
    },

    /**
     * @property {Number} stepSize - 步进距离
     */
    stepSize : {
        get : function() {
            if (this.numberOfStep > 1) {
                return this.length / (this.numberOfStep - 1)
            }
            else if (this.style === ProgressBar.STYLE_CIRCLE) {
                return this.length / 360;
            }
            else if (this.style === ProgressBar.STYLE_HORIZONTAL) {
                return this.length / (this._slidingRect ? this._slidingRect.width : this.width);
            }
            else {
                return this.length / (this._slidingRect ? this._slidingRect.height : this.height);
            }
        }
    },

    /**
     * @property {Number} fixedSize - 循环播放时的固定大小，值为占进度条 区域的百分比，1为整个区域, < 0 时表示不限制大小
     */
    fixedSize : {
        get : function() { return this._fixedSize; },
        set : function(value) { 
            if (this._fixedSize === value) {
                return;
            }
            this._fixedSize = value; 
            // 更新
            this._updateSliders();
        }
    },

    /**
     * @property {Phaser.Signal} onValueChange - 当进度值发生变化时的事件
     * @readonly
     */
    onValueChange : {
        get : function() {
            if (!this._onValueChange) {
                this._onValueChange = new Phaser.Signal();
            }
            return this._onValueChange;
        }
    },

    /**
     * @property {boolean} clipSliders - 是否裁切滑块
     */
    clipSliders : {
        get : function() {
            if (!this.sliders) {
                return false;
            }
            var mask = this.sliders.getScript(qc.NodeMask);
            return mask && mask.enable;
        },
        set : function(value) {
            this._setClipSliders(value);
        }
    },

    /**
     * @property {Number} showMode - 显示模式，显示进度部分还是显示剩余部分
     */
    showMode : {
        get : function() { return this._showMode; },
        set : function(value) {
            this._setShowMode(value);
        }
    },

    /**
     * @property {boolean} reverse - 反向显示
     */
    reverse : {
        get : function() { return this._reverse; },
        set : function(value) {
            this._reverse = value;
            this._updateSliders();
        }
    },

    /**
     * @property {Number} startRadian - 开始的弧度，仅当style === STYLE_CIRCLE时有效
     */
    startRadian : {
        get : function() { return this._startRadian|| 0; },
        set : function(value) {
            this.setCircleScope(value);
        }
    },

    /**
     * @property {Number} endRadian -  结束的弧度，仅当style === STYLE_CIRCLE时有效，endRadian > startRadian && endRadian <= startRadian + Math.PI * 2
     */
    endRadian : {
        get : function() { return this._endRadian || Math.PI * 2; },
        set : function(value) {
            this.setCircleScope(null, value);
        }
    },

    /**
     * @property {Number} startAngle - 开始的角度，仅当style === STYLE_CIRCLE时有效
     */
    startAngle : {
        get : function() { return this.startRadian * 180 / Math.PI; },
        set : function(value) {
            this.setCircleAngle(value);
        }
    },

    /**
     * @property {Number} endAngle -  结束的角度，仅当style === STYLE_CIRCLE时有效，endRadian > startRadian && endRadian <= startRadian + Math.PI * 2
     */
    endAngle : {
        get : function() { return this.endRadian * 180 / Math.PI; },
        set : function(value) {
            this.setCircleAngle(null, value);
        }
    },

    /**
     * @property {Number} showRadian - 当前显示的弧度
     */
    showRadian : {
        get : function() { return this.endRadian - this.startRadian; }
    },

    /**
     * @property {qc.Rectangle} _slidingRect - 滑动区域
     * @readonly
     * @private
     */
    _slidingRect : {
        get : function() {
            if (!this.sliders || !this.sliders.parent)
                return null;
            else
                return this.sliders.parent.rect;
        }
    },

    /**
     * @property {'x'|'y'|'rotation'} _axisPos - 当前滚动条移动方向的位置参数
     * @readonly
     * @private
     */
    _axisPos : {
        get : function() {
            return this.style === ProgressBar.STYLE_CIRCLE ?
                'rotation' : (this.style === ProgressBar.STYLE_HORIZONTAL ? 'x' : 'y');
        }
    },

    /**
     * @property {'width'|'height'} _axisSize - 当前滚动条移动方向的大小参数
     * @readonly
     * @private
     */
    _axisSize : {
        get : function() {
            return this.style === ProgressBar.STYLE_CIRCLE ?
                'rotation' : (this.style === ProgressBar.STYLE_HORIZONTAL ? 'width' : 'height');
        }
    }
});

/**
 * 获取需要被序列化的信息描述
 * @override
 * @internal
 */
ProgressBar.prototype.getMeta = function() {
    var s = qc.Serializer;
    var json = qc.UIImage.prototype.getMeta.call(this);
    json.style = qc.Serializer.NUMBER;
    json.sliders = qc.Serializer.NODE;
    json._minValue = qc.Serializer.NUMBER;
    json._maxValue = qc.Serializer.NUMBER;
    json.value = qc.Serializer.NUMBER;
    json._startRadian = qc.Serializer.NUMBER;
    json._endRadian = qc.Serializer.NUMBER;
    json.indeterminable = qc.Serializer.BOOLEAN;
    json.loopTime = qc.Serializer.NUMBER;
    json.numberOfStep = qc.Serializer.NUMBER;
    json.fixedSize = qc.Serializer.NUMBER;
    json.clipSliders = qc.Serializer.BOOLEAN;
    json.showMode = qc.Serializer.NUMBER;
    json.reverse = qc.Serializer.NUMBER;
    return json;
};

/**
 * 析构
 */
ProgressBar.prototype.onDestroy = function() {
    qc.UIImage.prototype.onDestroy.call(this);
    this.sliders = null;
};

/**
 * 重置滑块信息
 * @private
 */
ProgressBar.prototype._resetSliders = function() {
    if (!this.sliders) {
        return;
    }
    this.sliders.rotation = 0;
    this.sliders.setAnchor(new qc.Point(0, 0), new qc.Point(1, 1), false);
    var mask = this.sliders.getScript(qc.NodeMask);
    if (mask) {
        mask.setMask(new qc.Point(0, 0), new qc.Point(1, 1), 0, Math.PI * 2);
    }
};

/**
 * 设置当前的显示样式
 * @param style {Number} - 显示的样式
 * @private
 */
ProgressBar.prototype._setStyle = function(style) {
    if (this._style === style)
        return;

    this._style = style;
    this._resetSliders();
    this._updateSliders();
};

/**
 * 裁切模式
 * @param clip {boolean} - 是否对进度条滑块进行裁切
 * @private
 */
ProgressBar.prototype._setClipSliders = function(clip) {
    if (this.clipSliders === clip)
        return;

    if (this.sliders) {
        this._resetSliders();
        var mask = this.sliders.getScript(qc.NodeMask);
        if (!mask && clip) {
            mask = this.sliders.addScript('qc.NodeMask');
        }
        if (mask) {
            mask.enable = clip;
        }
    }

    this._updateSliders();
};

/**
 * 设置进度条的显示模式，
 * @param mode {Number} - 显示方式，滑块用来表示已经经过的部分还是表示未经过的部分
 * @private
 */
ProgressBar.prototype._setShowMode = function(mode) {
    if (this._showMode === mode) {
        return;
    }
    this._showMode = mode;
    if (this.sliders) {
        this._resetSliders();
    }
    this._updateSliders();
};

/**
 * 更新值
 */
ProgressBar.prototype.update = function() {
    if (this._indeterminable) {
        this._processIndeterminable();
    }
};

/**
 * 设置最大最小值
 * @param min {Number} - 最小值
 * @param max {Number} - 最大值
 */
ProgressBar.prototype.setMinMax = function(min, max) {
    min = typeof min === 'undefined' || min === null ? this.minValue : min;
    max = typeof max === 'undefined' || max === null ? this.maxValue : max;
    if (min >= max) {
        throw new Error('Expected:min < max');
    }
    this._minValue = min;
    this._maxValue = max;
    this._setValue(this.value, true);
};

/**
 * 设置旋转显示时的起始结束角度
 * @param start {Number} - 起始弧度
 * @param end {Number} - 结束弧度
 */
ProgressBar.prototype.setCircleScope = function(start, end) {
    start = typeof start === 'undefined' || start === null ? this.startRadian : start;
    end = typeof end === 'undefined' || end === null ? this.endRadian : end;
    if (start >= end || end > start + Math.PI * 2) {
        throw new Error('Expected:start < end and end <= 2*PI + start');
    }
    this._startRadian = start;
    this._endRadian = end;

    // 更新
    this._updateSliders();
};

/**
 * 设置旋转显示时的起始结束角度
 * @param start {Number} - 起始角度
 * @param end {Number} - 结束角度
 */
ProgressBar.prototype.setCircleAngle = function(start, end) {
    start = typeof start === 'undefined' || start === null ? this.startAngle : start;
    end = typeof end === 'undefined' || end === null ? this.endAngle : end;
    if (start >= end || end > start + 360) {
        throw new Error('Expected:start < end and end <= 360 + start');
    }
    this._startRadian = start * Math.PI / 180;
    this._endRadian = end * Math.PI / 180;

    // 更新
    this._updateSliders();
};

/**
 * 设置当前的进度值
 * @param value {Number} - 当前的进度值
 * @param notify {boolean} - 是否通知值改变
 * @private
 */
ProgressBar.prototype._setValue = function(value, notify) {
    var currValue = this.stepValue;
    this._value = Phaser.Math.clamp(value, this.minValue, this.maxValue);

    if (currValue !== this.stepValue) {
        // 更新
        this._updateSliders();
    }

    if (notify) {
        this.onValueChange.dispatch(this.value);
    }
};

/**
 * 不确定模式下的进度调整
 * @private
 */
ProgressBar.prototype._processIndeterminable = function() {
    this._loopValue = this._loopValue || this.value;
    this._loopValue += this.length * this.game.time.deltaTime / this.loopTime;
    if (this._loopValue > this.maxValue + this.stepSize) {
        this._loopValue = this.minValue;
    }
    this.value = this._loopValue;
};

/**
 * 更新滑块的信息
 * @private
 */
ProgressBar.prototype._updateSliders = function() {
    if (!this._slidingRect) {
        return;
    }

    var mask = this.sliders.getScript(qc.NodeMask);
    var value = (this.stepValue - this.minValue) / this.length;
    if (this.reverse) {
        value = 1 - value;
    }
    if (!this.clipSliders || !mask) {
        switch (this.style) {
            case ProgressBar.STYLE_HORIZONTAL:
            case ProgressBar.STYLE_VERTICAL:
                // 滑动时的实际区域为 1 + this.fixedSize
                var minAnchor = new qc.Point(0, 0);
                var maxAnchor = new qc.Point(1, 1);
                var axisPos = this._axisPos;
                if (this.fixedSize >= 0) {
                    if (this._canSlideOut) {
                        value *= 1 + this.fixedSize;
                        // fixedSize模式下，没有SHOW_REMAINED模式
                        maxAnchor[axisPos] = Math.min(1, value);
                        minAnchor[axisPos] = Math.max(0, value - this.fixedSize - 0.00001);
                    }
                    else {
                        value *= 1 - this.fixedSize;
                        minAnchor[axisPos] = Math.max(0, value);
                        maxAnchor[axisPos] = Math.min(1, value + this.fixedSize + 0.00001);
                    }
                }
                else {
                    if (this.showMode === ProgressBar.SHOW_PROCESSED) {
                        maxAnchor[axisPos] = Math.max(0.00001, Math.min(1, value));
                    }
                    else {
                        minAnchor[axisPos] = Math.min(1 - 0.00001, value);
                    }
                }
                this.sliders.setAnchor(minAnchor, maxAnchor, false);

                break;
            case ProgressBar.STYLE_CIRCLE:
                // 滚动
                // 滑动模式下，没有SHOW_REMAINED模式
                this.sliders.rotation = value * this.showRadian + this.startRadian;
                break;
        }
    }
    else {
        switch (this.style) {
            case ProgressBar.STYLE_HORIZONTAL:
            case ProgressBar.STYLE_VERTICAL:
                // 滑动
                var minAnchor = new qc.Point(0, 0);
                var maxAnchor = new qc.Point(1, 1);
                var axisPos = this._axisPos;
                if (this.fixedSize >= 0) {
                    if (this._canSlideOut) {
                        value *= 1 + this.fixedSize;
                        // fixedSize模式下，没有SHOW_REMAINED模式
                        maxAnchor[axisPos] = Math.min(1, value);
                        minAnchor[axisPos] = Math.max(0, value - this.fixedSize - 0.00001);
                    }
                    else {
                        value *= 1 - this.fixedSize;
                        minAnchor[axisPos] = Math.max(0, value);
                        maxAnchor[axisPos] = Math.min(1, value + this.fixedSize + 0.00001);
                    }
                }
                else {
                    if (this.showMode === ProgressBar.SHOW_PROCESSED) {
                        maxAnchor[axisPos] = Math.min(1, value);
                    }
                    else {
                        minAnchor[axisPos] = Math.min(1, value);
                    }
                }
                mask.setMask(minAnchor, maxAnchor);
                break;
            case ProgressBar.STYLE_CIRCLE:
                // 滚动
                if (this.fixedSize >= 0) {
                    var minRotation = this.startRadian + (value - this.fixedSize) * this.showRadian ;
                    var maxRotation = this.startRadian + value * this.showRadian;
                    if (this.showMode === ProgressBar.SHOW_REMAINED && this.showRadian === Math.PI * 2) {
                        var tempMin = maxRotation;
                        maxRotation = minRotation + Math.PI * 2;
                        minRotation = tempMin;
                    }
                    mask.setMask(null, null, minRotation, maxRotation);
                }
                else {
                    var minRotation = -Math.PI / 2 + this.startRadian;
                    var maxRotation = -Math.PI / 2 + this.endRadian;
                    if (this.showMode === ProgressBar.SHOW_PROCESSED) {
                        maxRotation = minRotation + value * this.showRadian;
                    }
                    else {
                        minRotation = minRotation + value * this.showRadian;
                    }
                    mask.setMask(null, null, minRotation, maxRotation);
                }
                break;
        }
    }
};

/**
 * 水平进度条
 * @constant
 * @type {number}
 */
ProgressBar.STYLE_HORIZONTAL = 0;

/**
 * 竖直进度条
 * @constant
 * @type {number}
 */
ProgressBar.STYLE_VERTICAL = 1;

/**
 * 圆形进度条
 * @constant
 * @type {number}
 */
ProgressBar.STYLE_CIRCLE = 2;

/**
 * 显示进行了的部分
 * @constant
 * @type {number}
 */
ProgressBar.SHOW_PROCESSED = 0;

/**
 * 显示剩余的部分
 * @constant
 * @type {number}
 */
ProgressBar.SHOW_REMAINED = 1;
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 滑动条控件，继承qc.ProgressBar
 * @class qc.Slider
 * @param {qc.Game} game - 游戏对象
 * @param {qc.Node | null} parent - 父亲节点
 * @constructor
 */
var Slider = qc.Slider = function(game, parent, uuid) {
    qc.ProgressBar.call(this, game, parent, uuid);

    /**
     * @property {string} name - 控件默认名字
     */
    this.name = 'Slider';

    this.canPursue = true;
    this._canSlideOut = false;

    // 监听滚动事件和拖拽事件
    this.onWheel.add(this._doWheel, this);
    this.onDown.add(this._doDown, this);
    this.onUp.add(this._doUp, this);
    this.onDragStart.add(this._doDragStart, this);
    this.onDrag.add(this._doDrag, this);
    this.onDragEnd.add(this._doDragEnd, this);

    var restore = uuid !== undefined;
    if (restore !== true) {
        // 初始状态为默认状态
        this.state = qc.UIState.NORMAL;
        // 挂载交互效果脚本
        var behaviour = this.addScript('qc.TransitionBehaviour');
        behaviour.target = this.sliders;
        behaviour.transition = qc.Transition.TEXTURE_SWAP;
    }
};
Slider.prototype = Object.create(qc.ProgressBar.prototype);
Slider.prototype.constructor = Slider;

Object.defineProperties(Slider.prototype, {
    /**
     * @property {number} state - 按钮的状态
     */
    state : {
        get : function()  { return this._state || qc.UIState.NORMAL; },
        set : function(v) {
            if (this.state === v) return;
            this._state = v;
            if (this._onStateChange) {
                this._onStateChange.dispatch();
            }
        }
    },

    /**
     * @property {qc.Node} sliders - 滑块，用来表示进度
     * @override
     */
    sliders : {
        get : function() {
            if (this._sliders && this._sliders._destroy) {
                this._sliders = null;
            }
            return this._sliders;
        },
        set : function(value) {
            this._sliders = value;

            // 改变交互对象目标
            var behaviour = this.getScript('qc.TransitionBehaviour');
            if (behaviour) {
                behaviour.target = value ? value : this;
            }

            this._updateSliders();
        }
    },

    /**
     * @property {qc.Signal} onStateChanged - 状态发生变化的事件
     */
    onStateChange : {
        get : function() {
            if (!this._onStateChange) {
                this._onStateChange = new qc.Signal();
            }
            return this._onStateChange;
        }
    },

    /**
     * @property {string} class - 类名
     * @readonly
     * @internal
     */
    class : {
        get: function () {
            return 'qc.Slider';
        }
    },

    /**
     * @property canPursue {boolean} - 是否允许可以追赶光标
     */
    canPursue : {
        get : function() { return this._canPursue; },
        set : function(value) { this._canPursue = value; }
    },

    /**
     * 是否允许交互，默认为false，控制onDown/onUp/onClick等事件
     *
     * @property {boolean} interactive
     * @override
     */
    'interactive': {
        get: function() {
            return this._interactive;
        },
        set: function(v) {
            this._interactive = v;
            if (this._onInteractive) {
                this._onInteractive.dispatch();
            }
            this.state = this.interactive ? qc.UIState.NORMAL : qc.UIState.DISABLED;
        }
    }
});

/**
 * 获取需要被序列化的信息描述
 * @override
 * @internal
 */
Slider.prototype.getMeta = function() {
    var s = qc.Serializer;
    var json = qc.ProgressBar.prototype.getMeta.call(this);
    json.canPursue = qc.Serializer.BOOLEAN;
    return json;
};


/**
 * 更新值
 */
Slider.prototype.update = function() {
    qc.ProgressBar.prototype.update.call(this);
    if (this._isMoveNotDragging && this._canPursue) {
        this._doPursued();
    }
};

/**
 * 设置当前的显示样式
 * @param style {Number} - 显示的样式
 * @private
 * @override
 */
Slider.prototype._setStyle = function(style) {
    qc.ProgressBar.prototype._setStyle.call(this, style);
    this.fixedSize = style === Slider.STYLE_CIRCLE ? 0 : -1;
};

/**
 * 计算点当前对应的值
 * @param {qc.Point} barPoint - 在滑块区域的位置
 * @param {qc.Point} isDrag -  是否是拖拽中
 * @return {Number}
 * @private
 */
Slider.prototype._calcPointValue = function(barPoint, isDrag) {
    var value = 0;
    var rect = this.rect;
    switch (this.style) {
        case Slider.STYLE_HORIZONTAL:
        case Slider.STYLE_VERTICAL:
            value = this.minValue + (barPoint[this._axisPos] - rect[this._axisPos]) * this.length / rect[this._axisSize];
            break;
        case Slider.STYLE_CIRCLE:
            var PI2 = 2 * Math.PI;
            var tmp = Math.atan2(barPoint.x - rect.centerX, rect.centerY - barPoint.y);
            if (tmp < 0) {
                tmp += PI2;
            }
            if (isDrag) {
                if (typeof this._tempDragAngle === 'undefined' || this._tempDragAngle === null) {
                    this._tempDragAngle = tmp;
                    this._recordDragAngle = tmp;
                }
                else {
                    var diff = tmp - this._tempDragAngle;
                    this._tempDragAngle = tmp;
                    if (Math.abs(diff) > Math.PI) {
                        diff = Phaser.Math.sign(diff) * (PI2 - Math.abs(diff));
                    }

                    this._recordDragAngle += diff;
                    tmp = this._recordDragAngle;
                }
            }
            value = this.minValue + (tmp - this.startRadian) * this.length / this.showRadian;
            break;
    }
    if (this.reverse) {
        value = this.maxValue - value + this.minValue;
    }
    return value;
};

/**
 * 滚动条滚动时
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.WheelEvent} - 拖拽结束事件
 * @private
 */
Slider.prototype._doWheel = function(node, event) {
    if (!this.sliders) {
        return;
    }

    var movement = Math.abs(event.source.deltaX) >= Math.abs(event.source.deltaY) ?
        event.source.deltaX : event.source.deltaY;
    var value = this.value;
    value += (this.reverse ? -1 : 1) * Math.sign(movement) * this.stepSize;
    this.value = value;
};

/**
 * 当按下时
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.PointerEvent) - 点击事件
 * @private
 */
Slider.prototype._doDown = function(node, event) {
    if (!this.sliders || event.source.eventId != qc.Mouse.BUTTON_LEFT) {
        return;
    }
    this.state = qc.UIState.PRESSED;
    // 记录记录的拖拽角度信息
    this._tempDragAngle = null;
    this._recordDragAngle = null;

    var globalPoint = new qc.Point(event.source.x, event.source.y);
    if (!this.sliders.rectContains(globalPoint)) {
        var barPoint = this.toLocal(globalPoint);
        this._isMoveNotDragging = true;
        this._pursuedPoint = this._calcPointValue(barPoint);
        this._pursuedType = 0;
    }
};

/**
 * 当弹起时
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.PointerEvent) - 点击事件
 * @private
 */
Slider.prototype._doUp = function(node, event) {
    this._isMoveNotDragging = false;
    if (this.state === qc.UIState.PRESSED) {
        this.state = qc.UIState.NORMAL;
    }
};

/**
 * 开始拖拽
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.DragStartEvent} - 开始拖拽事件
 * @private
 */
Slider.prototype._doDragStart = function(node, event) {
    if (!this.sliders || event.source.eventId != qc.Mouse.BUTTON_LEFT) {
        return;
    }

    this.state = qc.UIState.PRESSED;
    this._isMoveNotDragging = false;
    var globalPoint = new qc.Point(event.source.x, event.source.y);
    var barPoint = this.toLocal(globalPoint);
    // 记录当前点击点距离虚拟滑块中心的位置
    this._offset = this._calcPointValue(barPoint, true) - this.value;
};

/**
 * 处理拖拽结束
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.DragEndEvent} - 拖拽结束事件
 * @private
 */
Slider.prototype._doDragEnd = function(node, event) {
    this._isMoveNotDragging = false;
    this._tempDragAngle = null;
    this._recordDragAngle = null;
    if (this.state === qc.UIState.PRESSED) {
        this.state = qc.UIState.NORMAL;
    }
};

/**
 * 处理拖拽事件
 * @param node {qc.Node} - 事件发生的节点
 * @param event {qc.DragEvent} - 拖拽结束事件
 * @private
 */
Slider.prototype._doDrag = function(node, event) {
    if (!this.sliders || event.source.eventId != qc.Mouse.BUTTON_LEFT) {
        return;
    }
    var globalPoint = new qc.Point(event.source.x, event.source.y);
    var barPoint = this.toLocal(globalPoint);
    var nowValue = this._calcPointValue(barPoint, true) - this._offset;
    this._setValue(nowValue, true);
};

/**
 * 滑块追赶点击位置
 * @private
 */
Slider.prototype._doPursued = function() {
    var stepSize = this.stepSize;
    var loop = Math.max(1, Math.ceil(this.length / 10 / stepSize));
    while (loop--) {
        if (this._pursuedPoint >= this.value &&
            this._pursuedPoint <= this.value + stepSize) {
            this._isMoveNotDragging = false;
            this._offset = this._pursuedPoint - this.value;
            this._pursuedPoint = 0;
            this._pursuedType = 0;
            return;
        }

        if ((this._pursuedPoint < this.value) ^ this.reverse) {
            if (this._pursuedType !== 2) {
                this.value -= stepSize;
                this._pursuedType = 1;
            }
        }
        else {
            if (this._pursuedType !== 1) {
                this.value += stepSize;
                this._pursuedType = 2;
            }
        }
    }
};

/**
 * 水平进度条
 * @constant
 * @type {number}
 */
Slider.STYLE_HORIZONTAL = qc.ProgressBar.STYLE_HORIZONTAL;

/**
 * 竖直进度条
 * @constant
 * @type {number}
 */
Slider.STYLE_VERTICAL = qc.ProgressBar.STYLE_VERTICAL;

/**
 * 圆形进度条
 * @constant
 * @type {number}
 */
Slider.STYLE_CIRCLE = qc.ProgressBar.STYLE_CIRCLE;

/**
 * 显示进行了的部分
 * @constant
 * @type {number}
 */
Slider.SHOW_PROCESSED = qc.ProgressBar.SHOW_PROCESSED;

/**
 * 显示剩余的部分
 * @constant
 * @type {number}
 */
Slider.SHOW_REMAINED = qc.ProgressBar.SHOW_REMAINED;
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 文本输入框控件
 * @class qc.InputField
 * @param {qc.Game} game
 * @constructor
 * @internal
 */
var InputField = qc.InputField = function(game, parent, uuid) {
    // 调用基类的初始
    var self = this;
    qc.UIImage.call(self, game, parent, uuid);

    // 初始化默认的名字
    self.name = 'InputField';

    /**
     * @property {qc.Signal} onStateChange - 状态发生变化的事件
     */
    self.onStateChange = new qc.Signal();

    /**
     * @property {qc.Signal} onValueChange - 文本发生变化的事件
     */
    self.onValueChange = new qc.Signal();

    // 设置我可以交互
    self.interactive = true;

    // 创建div做交互，否则有些手机无法在input.nativeMode为false时无法弹出输入键盘
    var div = self.div = document.createElement('div');
    var style = div.style;
    style.setProperty("-webkit-tap-highlight-color", "rgba(0, 0, 0, 0)", null);
    style.position = 'absolute';
    style.padding = 0;
    style.margin = 0;
    style.border = 0;
    style.outline = 0;
    style.background = 'none';
    self.game.world.frontDomRoot.appendChild(div);
    
    var restore = uuid !== undefined;
    if (restore !== true) {
        // 挂载显示文本的节点
        var tc = self.textComponent = game.add.text(self);
        tc.text = '';
        tc.name = 'Text';
        tc.setAnchor(new qc.Point(0, 0), new qc.Point(1, 1));
        tc.left = 5;
        tc.right = 5;
        tc.top = 5;
        tc.bottom = 5;
        tc.interactive = false;
        tc.color = Color.black;
        tc.alignV = UIText.MIDDLE;
        tc.alignH = UIText.LEFT;

        // 挂载输入提示语的节点
        var ph = self.placeholder = game.add.text(self);
        ph.text = 'Enter text...';
        ph.name = 'Placeholder';
        ph.setAnchor(new qc.Point(0, 0), new qc.Point(1, 1));
        ph.left = 5;
        ph.right = 5;
        ph.top = 5;
        ph.bottom = 5;
        ph.interactive = false;
        ph.color = Color.grey;
        ph.alignV = UIText.MIDDLE;
        ph.alignH = UIText.LEFT;

        // 我的初始状态为默认状态
        this.state = qc.UIState.NORMAL;

        // 挂载交互效果脚本
        var behaviour = this.addScript('qc.TransitionBehaviour');
        behaviour.target = this;
    }

    // 处理点击事件开始编辑
    var processNativeDown = function(event){
        event.preventDefault();
        if (self.state !== qc.UIState.DISABLED) {
            self.startEditing();
        }
    };
    div.addEventListener('mouseup', processNativeDown, false);
    div.addEventListener('touchend', processNativeDown, false);
    
    // 监听点击事件切换state状态
    this.onDown.add(function() {
        if (self.state !== qc.UIState.DISABLED) {
            self.state = qc.UIState.PRESSED;
        }
    });
    this.onUp.add(function() {
        if (self.state === qc.UIState.PRESSED) {
            self.state = qc.UIState.NORMAL;
        }
    });
    
    
    // 初始化设置为空
    this.text = '';

    // 设置默认宽高
    this.width = 120;
    this.height = 30;
};

/**
 * 输入框的行类型：单行、多行
 * @type {number}
 */
InputField.SINGLE_LINE = 0;
InputField.MULTI_LINE = 1;

/**
 * 输入的内容类型：标准、整数、数字、电话号码、邮件、密码等
 * @type {number}
 */
InputField.STANDARD = 0;
InputField.INT = 1;
InputField.NUMBER = 2;
InputField.TEL = 3;
InputField.EMAIL = 4;
InputField.PASSWORD = 5;

InputField.prototype = Object.create(qc.UIImage.prototype);
InputField.prototype.constructor = InputField;

// 最大字符限制
InputField.prototype._characterLimit = -1;

// 输入框类型
InputField.prototype._lineType = InputField.SINGLE_LINE;

// 输入内容类型
InputField.prototype._contentType = InputField.STANDARD;

// 弹出输入框
InputField.prototype._input = null;

// 密码实际值
InputField.prototype._password = '';

/**
 * @property {qc.UIText} textComponent - 关联的文本对象
 */
InputField.prototype.textComponent = null;

/**
 * @property {qc.UIText} placeholder - 关联的输入提示对象
 */
InputField.prototype.placeholder = null;

Object.defineProperties(InputField.prototype, {
    /**
     * @property {string} class - 类名字
     * @readonly
     * @internal
     */
    'class' : {
        get : function() { return 'qc.InputField' }
    },

    /**
     * @property {number} state - 输入框的状态
     */
    state : {
        get : function()  { return this._state || qc.UIState.NORMAL; },
        set : function(v) {
            if (this.state === v) return;
            this._state = v;
            this.onStateChange.dispatch();
        }
    },

    /**
     * @property {number} type - 编辑框类型(单行，多行)
     */
    lineType : {
        get : function()  { return this._lineType; },
        set : function(v) { this._lineType = v;    }
    },

    /**
     * @property {number} contentType - 编辑框的内容限制
     */
    contentType : {
        get : function() { return this._contentType; },
        set : function(v) {
            if (this._contentType === v) return;
            this._contentType = v;
            this.text = this.text;
        }
    },

    textComponent: {
        get: function() {
            if (this._textComponent && this._textComponent._destroy) {
                this._textComponent = null;
            }
            return this._textComponent;
        },
        set: function(value) {
            this._textComponent = value;
        }
    },

    placeholder: {
        get: function() {
            if (this._placeholder && this._placeholder._destroy) {
                this._placeholder = null;
            }
            return this._placeholder;
        },
        set: function(value) {
            this._placeholder = value;
        }
    },

    placeholderText: {
        get: function() {
            var placeholder = this.placeholder;
            if (!placeholder) {
                return '';
            }
            return placeholder.text;
        },
        set: function(value) {
            var placeholder = this.placeholder;
            if (placeholder) {
                placeholder.text = value;
            }
        }
    },

    /**
     * @property {string} text - 编辑框内容
     */
    text : {
        get : function() {
            var textComponent = this.textComponent;
            if (!textComponent) {
                return "";
            }
            if (this._contentType === InputField.PASSWORD) {
                return this._password;
            }
            return this.textComponent.text;
        },
        set : function(v) {
            if (!this.textComponent) {
                return;
            }
            v = this.toValidateText(v);
            if (v === this.text){
                return;
            }
            if (this._contentType === InputField.PASSWORD) {
                this._password = v;
                var password = '';
                for (var i = 0; i < v.length; i++) {
                    password += '·';
                }
                this.textComponent.text = password;
            }
            else {
                this.textComponent.text = v;
            }
            this.onValueChange.dispatch(v);
        }
    },

    /**
     * @property {number} characterLimit - 字符数限制
     */
    characterLimit : {
        get : function() { return this._characterLimit; },
        set : function(v) {
            if (this.characterLimit === v) return;
            this._characterLimit = v;
            this.text = this.text;
        }
    },

    /**
     * @property {boolean} overFlow  - 超出是否部分是否裁剪掉
     */
    'overflow' : {
        get : function() {
            if (!this.textComponent) {
                return false;
            }
            return this.textComponent.overflow;
        },
        set : function(v) {
            if (!this.textComponent) return;
            this.textComponent.overflow = v;
        }
    },

    /**
     * @property {boolean} isFocused  - 当前是否处于编辑状态
     */
    isFocused: {
        get: function() {
            return !!this.getInput();
        },
        set: function(value) {
            if (value) {
                this.startEditing();
            }
            else {
                this.stopEditing();
            }
        }
    }
});

/**
 * 获取需要被序列化的信息描述
 * @overide
 * @internal
 */
InputField.prototype.getMeta = function() {
    var s = qc.Serializer;
    var json = qc.UIImage.prototype.getMeta.call(this);

    json.lineType = s.NUMBER;
    json.contentType = s.NUMBER;
    json.placeholder = s.NODE;
    json.textComponent = s.NODE;
    json.text = s.STRING;
    json.overflow = s.BOOLEAN;
    json.state = s.NUMBER;
    json._password = s.STRING;
    json.characterLimit = s.NUMBER;
    return json;
};

/**
 * @overide
 * @internal
 */
InputField.prototype.update = function() {
    var input = this.getInput(),
        textComponent = this.textComponent,
        placeholder = this.placeholder;
    
    if (input) {
        if (textComponent) {
            textComponent.textPhaser.renderable = false;
        }
        if (placeholder) {
            placeholder.textPhaser.renderable = false;
        }
        this._updateInput(input);    
    }
    else {
        if (textComponent) {
            textComponent.textPhaser.renderable = true;
        }
        if (placeholder) {
            placeholder.textPhaser.renderable = this.text === '';
        } 
    }
    
    var div = this.div;
    var now = this.game.time.now;
    if (div._lastUpdate && now - div._lastUpdate < 200) {
        // 避免更新太频繁
        return;
    }
    div._lastUpdate = now;
    qc.Util.updateTransform(this, div);    
};

/**
 * 转换成有效的文字
 * @private
 */
InputField.prototype.toValidateText = function(text) {
    text = text || '';
    switch (this.contentType) {
        case InputField.INT :
            text = text.replace(/[^\d\-]/g, '');
            break;
        case InputField.NUMBER :
            text = text.replace(/[^\d\.\-]/g, '');
            break;
        case InputField.EMAIL :
            text = text.replace(/[^\w0-9\@\.]/g, '');
            break;
        case InputField.TEL :
            text = text.replace(/[^\d\(\)\+\-\s]/g, '');
            break;
        default :
            break;
    }
    if (this.characterLimit > 0 && text.length > this.characterLimit) {
        text = text.substr(0, this.characterLimit);
    }
    return text;
};

/**
 * 开始编辑
 */
InputField.prototype.startEditing = function() {
    var self = this,
        textComponent = self.textComponent;
    if (!textComponent) {
        return;
    }
    var input = this._input;
    if (!input) {
        // 创建TextArea组件
        if (this.lineType === InputField.MULTI_LINE) {
            input = self._input = document.createElement('textarea');
            input.onkeydown = function (event) {
                if (Keyboard.ESC === event.keyCode) {
                    self.cancelEditing();
                }
            };
        }
        // 创建普通Input组件
        else {
            input = self._input = document.createElement('input');
            input.onkeydown = function (event) {
                if (Keyboard.ENTER === event.keyCode) {
                    self.stopEditing();
                }
                else if (Keyboard.ESC === event.keyCode) {
                    self.cancelEditing();
                }
            };
            switch (self.contentType) {
                case InputField.PASSWORD :
                    input.type = 'password';
                    break;
                case InputField.EMAIL :
                    input.type = 'email';
                    break;
                case InputField.TEL :
                    input.type = 'tel';
                    break;
                case InputField.INT:
                case InputField.NUMBER:
                    input.type = 'number';
                    break;
                default :
                    input.type = 'text';
                    break;
            }
            switch (textComponent.alignH) {
                case UIText.LEFT:
                    input.style.textAlign = 'left';
                    break;
                case UIText.RIGHT:
                    input.style.textAlign = 'right';
                    break;
                default:
                    input.style.textAlign = 'center';
                    break;
            }
            switch (textComponent.alignV) {
                case UIText.TOP:
                    input.style.verticalAlign = 'top';
                    break;
                case UIText.BOTTOM:
                    input.style.verticalAlign = 'bottom';
                    break;
                default:
                    input.style.verticalAlign = 'middle';
                    break;
            }
        }
        if (this.characterLimit > 0) {
            input.setAttribute('maxlength', this.characterLimit);
        }
        input.value = this.text;
        var style = input.style;
        style.position = 'absolute';
        style.padding = 0;
        style.margin = 0;
        style.border = 0;
        style.outline = 0;
        style.background = 'none';
        input.onblur = function (event) {
            self.stopEditing();
        };
        input._handleClick = function(event) {
            // 点击在div组件上不做处理
            if (event.target !== self.div) {
                self.stopEditing();   
            }
        };
        var dom = self.game.world.frontDomRoot;
        dom.addEventListener('mousedown', input._handleClick, false);
        dom.addEventListener('touchstart', input._handleClick, false);
        self.game.input._inputting++;
        dom.appendChild(input);
        self._updateInput(input);
        input.focus();
    }
};

/**
 * 更新输入框位置等信息
 * @param input
 * @private
 */
InputField.prototype._updateInput = function(input) {
    var now = this.game.time.now;
    // 避免更新太频繁
    if (input._lastUpdate && now - input._lastUpdate < 100) {
        return;
    }
    input._lastUpdate = now;

    var textComponent = this.textComponent;
    if (textComponent.textPhaser instanceof Phaser.Text) {
        input.style.font = textComponent.textPhaser.style.font;
    }
    input.style.fontSize = textComponent.fontSize + 'px';
    input.style.color = textComponent.color.toString();
    qc.Util.updateTransform(textComponent, input);
    
    // 更新值
    if (input._lastValue !== input.value) {
        input._lastValue = input.value;
        this.text = input.value;
    }

};

InputField.prototype.getInput = function() {
    // 如果文本组件删除，则停止编辑
    if (this._input && !this.textComponent) {
        this.cancelEditing();
    }
    return this._input;
};

/**
 * 取消编辑
 */
InputField.prototype.cancelEditing = function() {
    var input = this._input;
    if (input) {
        this._input = null;
        this.game.input._inputting--;
        var dom = this.game.world.frontDomRoot;
        dom.removeEventListener('mousedown', input._handleClick, false);
        dom.removeEventListener('touchstart', input._handleClick, false);
        qc.Util.removeHTML(input);
    }
};

/**
 * 停止编辑
 */
InputField.prototype.stopEditing = function() {
    var input = this.getInput();
    if (!input) {
        return;
    }
    this.text = input.value;
    this.cancelEditing();
};

/**
 * 销毁对象前取消编辑
 */
InputField.prototype.onDestroy = function() {
    this.cancelEditing();
    qc.Util.removeHTML(this.div);
    qc.UIImage.prototype.onDestroy.call(this);
};

/**
 * 父亲或自身的可见属性发生变化了
 * @protected
 */
InputField.prototype.onVisibleChange = function() {
    this.div.style.display = this.isWorldVisible() ? 'block' : 'none';
};
// 本功能待提供
/**
 * @author linyw
 * copyright 2015 Qcplay All Rights Reserved.
 */

var Tilemap = qc.Tilemap = function(game, parent, uuid) {
    qc.Node.call(this, new Phaser.Group(game.phaser, null), parent, uuid);

    // 初始化默认的名字
    this.name = 'Tilemap';

};

Tilemap.prototype = Object.create(qc.Node.prototype);
Tilemap.prototype.constructor = Tilemap;

Tilemap.prototype._data = null;
Tilemap.prototype._scrollX = 0;
Tilemap.prototype._scrollY = 0;
Tilemap.prototype._layers = 0;
Tilemap.prototype._tilesets = 0;

/**
 * 获取需要被序列化的信息描述
 */
Tilemap.prototype.getMeta = function() {
    var json = qc.Node.prototype.getMeta.call(this);

    // 增加Tilemap需要序列化的内容
    json.data = Serializer.TEXTASSET;
    json.scrollX = Serializer.NUMBER;
    json.scrollY = Serializer.NUMBER;
    json.layers = Serializer.NODES;
    json.tilesets = Serializer.TEXTURES;

    return json;
};

Tilemap.prototype.generateLayers = function(callback) {
    var self = this;
    var data = self.data;
    if (data) {
        var game = self.game;
        var info = self._dataInfo;
        var json = info.json;
        var tilesets = json.tilesets;
        var items = [];
        for (var i = 0; i < tilesets.length; i++) {
            items.push({
                key: tilesets[i].name,
                url: info.prefix + tilesets[i].name + '.bin'
            });
        }
        // 加载图片资源
        game.assets.loadBatch(items, function() {
            // 保存图集数组
            var assetArray = [];
            for (var i = 0; i<items.length; i++) {
                assetArray.push(items[i].asset);
            }
            self.tilesets = assetArray;

            // 删除所有孩子
            self.removeChildren();
            // 生成图层对象
            var layerArray = [];
            for (var i = 0; i < json.layers.length; i++) {
                var layer = json.layers[i];
                if (layer.type === 'tilelayer') {
                    var tileLayer = new TileLayer(game, self);
                    tileLayer.name = layer.name;
                    tileLayer.tilemap = self;
                    tileLayer.layerIndex = i;
                    layerArray.push(tileLayer);
                }
                else if (layer.type === 'objectgroup') {
                    var objectLayer = new ObjectLayer(game, self);
                    objectLayer.name = layer.name;
                    objectLayer.tilemap = self;
                    objectLayer.layerIndex = i;
                    layerArray.push(objectLayer);
                }
            }
            // 保存图层数组
            self.layers = layerArray;
            if (callback) {
                callback();
            }
        });
    }
};

/**
 * 根据索引查找图层
 * @param index 图层索引
 * @returns {*}
 */
Tilemap.prototype.getLayerByIndex = function(index) {
    var layers = this._layers;
    if (layers && index >=0 && index < layers.length) {
        return layers[index];
    }
    return null;
};

/**
 * 根据名称查找图层
 * @param name 图层名称
 * @returns {*}
 */
Tilemap.prototype.getLayerByName = function(name) {
    var layers = this._layers;
    if (layers) {
        for (var i = 0; i < layers.length; i++) {
            if (layers[i].name === name) {
                return layers[i];
            }
        }
    }
    return null;
};

/**
 * 获取图层JSON数据信息
 * @param index 图层所在索引
 * @param type 图层类型 tilelayer|objectgroup
 * @returns {*}
 * @private
 */
Tilemap.prototype._getLayerJson = function(index, type) {
    if (!this._dataInfo) {
        return;
    }
    var layers = this._dataInfo.json.layers;
    if (index >= 0 && index < layers.length) {
        var layer = layers[index];
        if (layer.type === type) {
            return layer;
        }
    }
    return null;
};

/**
 * 重置为地图大小
 */
Tilemap.prototype.resetNativeSize = function() {
    var nativeSize = this.nativeSize;
    if (nativeSize) {
        this.width = nativeSize.width;
        this.height = nativeSize.height;
    }
};

/**
 * 获取指定位置所在的格子位置
 * @param x
 * @param y
 * @return {qc.Point}
 */
Tilemap.prototype.getTilePosition = function(x, y) {
    return new qc.Point(
        Math.floor(x / this.tileWidth),
        Math.floor(y / this.tileHeight)
    );
};

Object.defineProperties(Tilemap.prototype, {

    /**
     * @property {qc.Rectangle} nativeSize - 地图实际大小
     * @readonly
     */
    nativeSize : {
        get : function() {
            var dataInfo = this._dataInfo;
            if (dataInfo) {
                return new qc.Rectangle(0, 0, dataInfo.map.widthInPixels, dataInfo.map.heightInPixels);
            }
            return null;
        }
    },

    /**
     * 图层数据
     */
    layers: {
        get: function() {
            return this._layers;
        },
        set: function(value) {
            this._layers = value;
        }
    },

    /**
     * 图集数组
     */
    tilesets: {
        get: function() {
            return this._tilesets;
        },
        set: function(value) {
            this._tilesets = value;
        }
    },

    /**
     * 水平滚动距离
     */
    scrollX: {
        get: function() {
            return this._scrollX;
        },
        set: function(value) {
            this._scrollX = value;
        }
    },

    /**
     * 垂直滚动距离
     */
    scrollY: {
        get: function() {
            return this._scrollY;
        },
        set: function(value) {
            this._scrollY = value;
        }
    },

    /**
     * @property {number} tileWidth - 格子的宽度
     * @readonly
     */
    tileWidth: {
        get: function() {
            return this._dataInfo.json.tilewidth;
        }
    },

    /**
     * @property {number} tileHeight - 格子的高度
     * @readonly
     */
    tileHeight: {
        get: function() {
            return this._dataInfo.json.tileheight;
        }
    },

    /**
     * @property {number} mapWidth - 地图X轴上的格子数
     * @readonly
     */
    mapWidth: {
        get: function() {
            return this._dataInfo.json.width;
        }
    },

    /**
     * @property {number} mapHeight - 地图Y轴上的格子数
     * @readonly
     */
    mapHeight: {
        get: function() {
            return this._dataInfo.json.height;
        }
    },

    data: {
        get: function() {
            return this._data;
        },
        set: function(data) {
            var self = this,
                game = self.game;
            var info = null;
            if (data) {
                if (!(data instanceof TextAsset)) {
                    return;
                }
                try {
                    var json = JSON.parse(data.text);
                    if (!json.layers || !json.tilesets) {
                        throw new Error('Invalid tilemap data format');
                    }
                    // 存储地图json数据到phaser缓存
                    game.phaser.load.tilemap(data.url, null, json, Phaser.Tilemap.TILED_JSON);
                    // 构建地图
                    var map = game.phaser.add.tilemap(data.url);
                    // 计算所有图集中最大小图尺寸
                    var maxTileWidth = 0;
                    var maxTileHeight = 0;
                    for (var i = 0; i < map.tilesets.length; i++) {
                        maxTileWidth = Math.max(maxTileWidth, map.tilesets[i].tileWidth);
                        maxTileHeight = Math.max(maxTileHeight, map.tilesets[i].tileHeight);
                    }
                    map.maxTileWidth = maxTileWidth;
                    map.maxTileHeight = maxTileHeight;

                    var index = data.url.lastIndexOf('/');
                    info = {
                        json: json,
                        map: map,
                        prefix: index >= 0 ? data.url.substring(0, index + 1) : ''
                    };
                }
                catch(ex) {
                    game.log.error('Asset{0}/{1} Parse fail', data.key, data.url, ex);
                    qc.Util.popupError(ex.message); 
                    return;
                }
            }
            self._data = data;
            self._dataInfo = info;
        }
    },

    /**
     * @property {string} class - 类名
     * @internal
     * @readonly
     */
    class : {
        get : function() { return 'qc.Tilemap'; }
    }
});
/**
 * @author linyw
 * copyright 2015 Qcplay All Rights Reserved.
 */

var TileLayer = qc.TileLayer = function(game, parent, uuid) {
    var phaserSprite = new Phaser.Sprite(game.phaser);
    qc.Node.call(this, phaserSprite, parent, uuid);

    // 初始化默认的名字
    this.name = 'TileLayer';

    // 缓存参数信息
    this._cache = {};

    // 缓存canvas，用于局部更新时使用
    phaserSprite.copyCanvas = Phaser.Canvas.create(1, 1);
    phaserSprite.copyContext = phaserSprite.copyCanvas.getContext('2d');

    phaserSprite.canvas = Phaser.Canvas.create(1, 1);
    phaserSprite.context = phaserSprite.canvas.getContext('2d');
    phaserSprite.baseTexture = new PIXI.BaseTexture(phaserSprite.canvas);
    phaserSprite.texture = new PIXI.Texture(phaserSprite.baseTexture);

    this.setAnchor(new qc.Point(0, 0), new qc.Point(1, 1));
    this.setStretch(0, 0, 0, 0);
};

TileLayer.prototype = Object.create(qc.Node.prototype);
TileLayer.prototype.constructor = TileLayer;

TileLayer.prototype._tilemap = null;
TileLayer.prototype._layerIndex = -1;
TileLayer.prototype._scrollXRatio = 1;
TileLayer.prototype._scrollYRatio = 1;

/**
 * 获取需要被序列化的信息描述
 */
TileLayer.prototype.getMeta = function() {
    var json = qc.Node.prototype.getMeta.call(this);

    // 增加TileLayer需要序列化的内容
    json.tilemap = Serializer.NODE;
    json.layerIndex = Serializer.INT;
    json.scrollXRatio = Serializer.NUMBER;
    json.scrollYRatio = Serializer.NUMBER;

    return json;
};

TileLayer.prototype.getTile = function(x, y) {
    if (!this._cache ||
            !this._cache.phaserLayer ||
            !this._cache.phaserLayer.data ||
            !this._cache.phaserLayer.data[y])
        return undefined;
    return this._cache.phaserLayer.data[y][x];
};

TileLayer.prototype.getTileIndex = function(x, y) {
    if (!this._cache ||
        !this._cache.phaserLayer ||
        !this._cache.phaserLayer.data ||
        !this._cache.phaserLayer.data[y] ||
        !this._cache.phaserLayer.data[y][x])
        return Infinity;
    return this._cache.phaserLayer.data[y][x].index;
};
/**
 * 更新
 */
TileLayer.prototype.update = function() {
    var self = this;
    var tilemap = self.tilemap;

    // 未绑定地图不更新
    if (!tilemap) {
        return;
    }

    var redrawAll = false,
        cache = self._cache,
        phaser = self.phaser,
        phaserMap = tilemap._dataInfo.map;

    // 地图信息变化
    redrawAll = this._checkTileLayerChange(phaserMap) || redrawAll;

    // 没有对应图层信息不更新
    var phaserLayer = cache.phaserLayer;
    if (!phaserLayer) {
        return;
    }

    var width = Math.round(self.width),
        height = Math.round(self.height),
        scrollX = tilemap.scrollX,
        scrollY = tilemap.scrollY,
        scrollXRatio = self.scrollXRatio,
        scrollYRatio = self.scrollYRatio;

    // 尺寸发生变化
    redrawAll = this._checkSizeChange(width, height) || redrawAll;

    // 参数变化更新图层
    if (redrawAll ||
        cache.scrollX !== scrollX ||
        cache.scrollY !== scrollY ||
        cache.scrollXRatio !== scrollXRatio ||
        cache.scrollYRatio !== scrollYRatio) {

        var context = phaser.context,
            copyContext = phaser.copyContext,
            offsetX = Math.round(scrollX * scrollXRatio),
            offsetY = Math.round(scrollY * scrollYRatio),
            oldViewRect = cache.viewRect,
            viewRect = new Phaser.Rectangle(-offsetX, -offsetY, width, height),
            phaserData = phaserLayer.data;

        // 如果不是全部重绘，检测是否有缓存的内容可以复用
        if (!redrawAll) {
            var intersection = Phaser.Rectangle.intersection(oldViewRect, viewRect);
            if (intersection.width > 10 && intersection.height > 10) {
                var x = intersection.x - oldViewRect.x;
                var y = intersection.y - oldViewRect.y;
                var w = intersection.width;
                var h = intersection.height;
                // 清除缓冲区
                copyContext.clearRect(x, y, w, h);
                // 绘制相交区域到缓冲区
                copyContext.drawImage(phaser.canvas, x, y, w, h, x, y, w, h);
            }
            else {
                // 相交区域太小或者无相交则全部重绘
                redrawAll = true;
            }
        }

        // 清除整个界面
        context.clearRect(0, 0, width, height);
        context.translate(offsetX, offsetY);

        // 全部刷新
        if (redrawAll) {
            this._drawRect(context, phaserMap, phaserData, -offsetX, -offsetY, width, height);
        }
        // 局部刷新
        else {
            // 拷贝缓冲区相交部分内容到界面
            context.drawImage(phaser.copyCanvas, x, y, w, h, intersection.x, intersection.y, w, h);

            // 相交区域左上角在界面
            if (viewRect.contains(oldViewRect.left, oldViewRect.top)) {
                this._drawRect(context, phaserMap, phaserData,
                    viewRect.x, viewRect.y, intersection.x - viewRect.x, viewRect.height);
                this._drawRect(context, phaserMap, phaserData,
                    intersection.x, viewRect.y, viewRect.right - intersection.x, intersection.y - viewRect.y);
            }
            // 相交区域右上角在界面
            else if (viewRect.contains(oldViewRect.right, oldViewRect.top)) {
                this._drawRect(context, phaserMap, phaserData,
                    viewRect.x, viewRect.y, intersection.right - viewRect.x, intersection.y - viewRect.y);
                this._drawRect(context, phaserMap, phaserData,
                    intersection.right, viewRect.y, viewRect.right - intersection.right, viewRect.height);
            }
            // 相交区域左下角在界面
            else if (viewRect.contains(oldViewRect.left, oldViewRect.bottom)) {
                this._drawRect(context, phaserMap, phaserData,
                    viewRect.x, viewRect.y, intersection.x - viewRect.x, viewRect.height);
                this._drawRect(context, phaserMap, phaserData,
                    intersection.x, intersection.bottom, viewRect.right - intersection.x, viewRect.bottom - intersection.bottom);
            }
            // 相交区域右下角在界面
            else if (viewRect.contains(oldViewRect.right, oldViewRect.bottom)) {
                this._drawRect(context, phaserMap, phaserData,
                    viewRect.x, intersection.bottom, intersection.right - viewRect.x, viewRect.bottom - intersection.bottom);
                this._drawRect(context, phaserMap, phaserData,
                    intersection.right, viewRect.y, viewRect.right - intersection.right, viewRect.height);
            }
        }

        context.translate(-offsetX, -offsetY);

        phaser.texture.baseTexture.dirty();

        cache.scrollX = scrollX;
        cache.scrollY = scrollY;
        cache.scrollXRatio = scrollXRatio;
        cache.scrollYRatio = scrollYRatio;
        cache.viewRect = viewRect;
    }
};

// 检测TileLayer图层信息是否变化
TileLayer.prototype._checkTileLayerChange = function(phaserMap) {
    var cache = this._cache;
    var tilemap = this.tilemap;
    var layerIndex = this.layerIndex;

    if (cache.tilemap !== tilemap ||
        cache.layerIndex !== layerIndex) {
        cache.phaserLayer = null;
        var layerJson = tilemap._getLayerJson(layerIndex, 'tilelayer');
        if (layerJson) {
            var index = phaserMap.getLayerIndex(layerJson.name);
            if (index >= 0) {
                cache.phaserLayer = phaserMap.layers[index];
            }
        }
        // 保存地图信息
        cache.tilemap = tilemap;
        cache.layerIndex = layerIndex;

        return true;
    }
    return false;
};

// 检测TileLayer的尺寸是否变化
TileLayer.prototype._checkSizeChange = function(width, height) {
    var cache = this._cache;
    var phaser = this.phaser;
    var texture = phaser.texture;

    if (cache.width !== width ||
        cache.height !== height) {

        // 更新尺寸
        phaser.canvas.width = width;
        phaser.canvas.height = height;

        phaser.copyCanvas.width = width;
        phaser.copyCanvas.height = height;

        texture.frame.width = width;
        texture.frame.height = height;

        texture.width = width;
        texture.height = height;

        texture.crop.width = width;
        texture.crop.height = height;

        texture.baseTexture.width = width;
        texture.baseTexture.height = height;

        texture.baseTexture.dirty();

        // 保持尺寸
        cache.width = width;
        cache.height = height;

        return true;
    }
    return false;
};

// 在指定的矩形区域内绘制Tile块
TileLayer.prototype._drawRect = function(context, phaserMap, phaserData, x, y, width, height) {
    if (!width || !height) {
        return;
    }

    // tiled编辑器的贴图块以左下角为基准往上和右延伸，
    // 所以需要考虑图集最大贴图大于tile大小的边界情况。
    var maxTileWidth = phaserMap.maxTileWidth,
        maxTileHeight = phaserMap.maxTileHeight,
        tw = phaserMap.tileWidth,
        th = phaserMap.tileHeight,
        startX = Math.floor((maxTileWidth > tw ? x - maxTileWidth : x) / tw),
        startY = Math.floor(y / th),
        endX = Math.ceil((x + width) / tw),
        endY = Math.ceil((maxTileHeight > th ? y + height + maxTileHeight : y + height) / th);

    if (startX < 0) startX = 0;
    if (startY < 0) startY = 0;
    if (endX > phaserMap.width) endX = phaserMap.width;
    if (endY > phaserMap.height) endY = phaserMap.height;

    for (y = startY; y < endY; y++) {
        var rows = phaserData[y];
        for (x = startX; x < endX; x++) {
            this._drawTile(context, phaserMap, rows[x]);
        }
    }
};

// 绘制Tile单元块
TileLayer.prototype._drawTile = function(context, phaserMap, phaserTile) {
    // Phaser.Tile#index为gid信息，大于0才需要绘制
    var gid = phaserTile.index;
    if (gid <= 0) {
        return;
    }

    // [x, y, tilesetIndex]
    var tile = phaserMap.tiles[phaserTile.index];
    var tileset = phaserMap.tilesets[tile[2]];
    var asset = tileset.asset;

    // 尝试获取对应图片资源
    if (asset === undefined) {
        var url = this.tilemap._dataInfo.prefix + tileset.name + '.bin';
        var ts = this.tilemap.tilesets;
        if (ts) {
            for (var i=0; i<ts.length; i++) {
                if (ts[i].url === url) {
                    asset = ts[i];
                    break;
                }
            }
        }
        if (!asset) {
            tileset.asset = null;
        }else {
            tileset.asset = asset;
        }
    }
    if (asset) {
        context.drawImage(
            asset.img,
            tile[0],
            tile[1],
            tileset.tileWidth,
            tileset.tileHeight,
            phaserTile.worldX,
            phaserTile.worldY + phaserMap.tileHeight - tileset.tileHeight,
            tileset.tileWidth,
            tileset.tileHeight
        );
    }
};


Object.defineProperties(TileLayer.prototype, {

    tilemap: {
        get: function() {
            if (this._tilemap && this._tilemap._destroy) {
                this._tilemap = null;
            }
            return this._tilemap;
        },
        set: function(value) {
            if (value && !(value instanceof Tilemap)) {
                return;
            }
            this._tilemap = value;
        }
    },

    layerIndex: {
        get: function() {
            return this._layerIndex;
        },
        set: function(value) {
            this._layerIndex = value;
        }
    },

    /**
     * 水平滚动速率
     */
    scrollXRatio: {
        get: function() {
            return this._scrollXRatio;
        },
        set: function(value) {
            this._scrollXRatio = value;
        }
    },

    /**
     * 垂直滚动速率
     */
    scrollYRatio: {
        get: function() {
            return this._scrollYRatio;
        },
        set: function(value) {
            this._scrollYRatio = value;
        }
    },

    /**
     * @property {string} class - 类名
     * @internal
     * @readonly
     */
    class : {
        get : function() { return 'qc.TileLayer'; }
    }
});
/**
 * @author linyw
 * copyright 2015 Qcplay All Rights Reserved.
 */

var ObjectLayer = qc.ObjectLayer = function(game, parent, uuid) {
    qc.Node.call(this, new Phaser.Group(game.phaser, null), parent, uuid);

    // 初始化默认的名字
    this.name = 'ObjectLayer';

    this.setAnchor(new qc.Point(0, 0), new qc.Point(1, 1));
    this.setStretch(0, 0, 0, 0);
};

ObjectLayer.prototype = Object.create(qc.Node.prototype);
ObjectLayer.prototype.constructor = ObjectLayer;

ObjectLayer.prototype._tilemap = null;
ObjectLayer.prototype._layerIndex = -1;
ObjectLayer.prototype._scrollXRatio = 1;
ObjectLayer.prototype._scrollYRatio = 1;

/**
 * 获取需要被序列化的信息描述
 */
ObjectLayer.prototype.getMeta = function() {
    var json = qc.Node.prototype.getMeta.call(this);

    // 增加TileLayer需要序列化的内容
    json.tilemap = Serializer.NODE;
    json.layerIndex = Serializer.INT;
    json.scrollXRatio = Serializer.NUMBER;
    json.scrollYRatio = Serializer.NUMBER;

    return json;
};


/**
 * 更新
 */
ObjectLayer.prototype.update = function() {
    if (this._tilemap && this._tilemap._destroy) {
        this._tilemap = null;
    }
    var tilemap = this._tilemap;

    if (tilemap) {
        this.x = tilemap.scrollX * this.scrollXRatio;
        this.y = tilemap.scrollY * this.scrollXRatio;
    }
};

Object.defineProperties(ObjectLayer.prototype, {

    tilemap: {
        get: function() {
            if (this._tilemap && this._tilemap._destroy) {
                this._tilemap = null;
            }
            return this._tilemap;
        },
        set: function(value) {
            if (value && !(value instanceof Tilemap)) {
                return;
            }
            this._tilemap = value;
        }
    },

    layerIndex: {
        get: function() {
            return this._layerIndex;
        },
        set: function(value) {
            this._layerIndex = value;
        }
    },

    /**
     * 水平滚动速率
     */
    scrollXRatio: {
        get: function() {
            return this._scrollXRatio;
        },
        set: function(value) {
            this._scrollXRatio = value;
        }
    },

    /**
     * 垂直滚动速率
     */
    scrollYRatio: {
        get: function() {
            return this._scrollYRatio;
        },
        set: function(value) {
            this._scrollYRatio = value;
        }
    },

    /**
     * @property {string} class - 类名
     * @internal
     * @readonly
     */
    class : {
        get : function() { return 'qc.ObjectLayer'; }
    }
});
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * dom节点
 * @constructor
 * @internal
 */
var Dom = qc.Dom = function(game, parent, uuid) {
    var self = this;
    self.game = game;

    // 调用基类的初始
    qc.Node.call(this, new Phaser.Group(this.game.phaser, null), parent, uuid);
    self.name = 'dom';

    /**
     * @property {dom} div - 对应的dom节点
     * @readonly
     */
    var div = self.div = document.createElement('div');
    div.style.position = 'absolute';
    div.setAttribute('id', self.uuid);
    div._qc = self;
    self.overflow = 'hidden';

    /**
     * @property {boolean} serializable - innerHTML是否需要序列化
     */
    self.serializable = true;

    // WorldTransform改变后，需要重新计算dom的位置
    self.phaser.worldTransformChangedCallback = self._updateDomTransform;
    self.phaser.worldTransformChangedContext = self;

    // 默认挂载在上层
    this.pos = Dom.POS_FRONT;
};

Dom.prototype = Object.create(qc.Node.prototype);
Dom.prototype.constructor = Dom;

/**
 * DOM节点是放在背景，还是在最上层
 * @type {number}
 */
Dom.POS_BACK  = 0;
Dom.POS_FRONT = 1;

Object.defineProperties(Dom.prototype, {
    /**
     * @property {string} class - 类的名字
     * @internal
     */
    class : {
        get : function() { return 'qc.Dom'; }
    },

    /**
     * @property {int} pos - 节点放的位置，是在底层还是在上层
     */
    pos: {
        get: function() { return this._pos; },
        set: function(v) {
            if (this.pos === v) return;
            this._pos = v;

            // 重新挂载之
            if (this.div.parentNode)
                this.div.parentNode.removeChild(this.div);
            if (v === Dom.POS_BACK) {
                this.game.world.backDomRoot.appendChild(this.div);
                if (this.zIndex >= 0) this.zIndex = -1;
            }
            else {
                this.game.world.frontDomRoot.appendChild(this.div);
                if (this.zIndex <= 0) this.zIndex = 1;
            }
        }
    },

    /**
     * @property {number} zIndex - DOM的层次控制
     *   pos === Dom.POS_BACK: zIndex < 0
     *   pos === Dom.POS_FRONT: zIndex > 0
     */
    zIndex: {
        get: function() { return this.div.style.zIndex || 0; },
        set: function(v) {
            if (v === this.div.style.zIndex) return;
            if (this._pos === Dom.POS_BACK && v >= 0) v = -1;
            else if (this._pos === Dom.POS_FRONT && v <= 0) v = 1;

            this.div.style.zIndex = v;
        }
    },

    /**
     * @property {string} innerHTML - 内部的HTML元素
     */
    innerHTML: {
        get: function() { return this.div.innerHTML; },
        set: function(v) {
            this.div.innerHTML = v;
        }
    },

    /**
     * @property {string} className - 使用的样式表
     */
    className: {
        get: function() { return this.div.className; },
        set: function(v) { this.div.className = v; }
    },

    /**
     * @property {string} overflow - 内容超出大小的处理：visible、scroll、hidden、auto
     */
    overflow: {
        get: function() { return this._overflow; },
        set: function(v) {
            if (v === this._overflow) return;
            this._overflow = v;
            this.div.style.overflow = v;
        }
    }
});

/**
 * 父亲或自身的可见属性发生变化了
 * @protected
 */
Dom.prototype.onVisibleChange = function() {
    this.div.style.display = this.isWorldVisible() ? 'block' : 'none';
};

/**
 * @method onDestroy
 * @overide
 * @internal
 */
Dom.prototype.onDestroy = function() {
    // 释放div上的节点对象
    this.div._qc = null;

    if (this.div.parentNode)
        this.div.parentNode.removeChild(this.div);

    // 调用父类的析构
    qc.Node.prototype.onDestroy.call(this);
};

/**
 * 获取需要被序列化的信息描述
 * @overide
 * @internal
 */
Dom.prototype.getMeta = function() {
    var self = this;
    var s = qc.Serializer;
    var json = qc.Node.prototype.getMeta.call(this);

    // 增加Dom需要序列化的内容
    json.pos = s.NUMBER;
    json.className = s.STRING;
    json.overflow = s.STRING;
    json.innerHTML = {
        get : function(ob, context) {
            return ob.serializable ? [true, ob.innerHTML] : undefined;
        },
        set : function(context, v) {
            if (!v) {
                self.serializable = false;
                self.innerHTML = '';
            }
            else {
                self.serializable = true;
                self.innerHTML = v[1];
            }
        }
    };
    json.zIndex = s.INT;
    return json;
};

/**
 * 更新节点的位置
 * @private
 */
Dom.prototype._updateDomTransform = function() {
    qc.Util.updateTransform(this, this.div);
};


/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * Graphics，集合图形绘制
 * @constructor
 * @internal
 */
var Graphics = qc.Graphics = function(game, parent, uuid) {
    var self = this;
    self.game = game;

    // 调用基类的初始
    qc.Node.call(this, new Phaser.Graphics(this.game.phaser), parent, uuid);
    self.name = 'graphics';
};
Graphics.prototype = Object.create(qc.Node.prototype);
Graphics.prototype.constructor = Graphics;

Object.defineProperties(Graphics.prototype, {
    /**
     * @property {string} class - 类的名字
     * @internal
     */
    class: {
        get : function() { return 'qc.Graphics'; }
    },

    /**
     * The alpha value used when filling the Graphics object.
     *
     * @property fillAlpha
     * @type Number
     */
    fillAlpha: {
        get: function() { return this.phaser.fillAlpha; },
        set: function(v) { this.phaser.fillAlpha = v; }
    },

    /**
     * The width (thickness) of any lines drawn.
     *
     * @property lineWidth
     * @type Number
     */
    lineWidth: {
        get: function() { return this.phaser.lineWidth; },
        set: function(v) { this.phaser.lineWidth = v; }
    },

    /**
     * The color of any lines drawn.
     *
     * @property lineColor
     * @type String
     * @default 0
     */
    lineColor: {
        get: function() { return this.phaser.lineColor; },
        set: function(v) { this.phaser.lineColor = v; }
    },

    /**
     * The blend mode to be applied to the graphic shape. Apply a value of qc.blendModes.NORMAL to reset the blend mode.
     *
     * @property blendMode
     * @type Number
     * @default PIXI.blendModes.NORMAL;
     */
    blendMode: {
        get: function() { return this.phaser.blendMode; },
        set: function(v) { this.phaser.blendMode = v; }
    },
});

/**
 * 获取需要被序列化的信息描述
 * @overide
 * @internal
 */
Graphics.prototype.getMeta = function() {
    var self = this;
    var s = qc.Serializer;
    var json = qc.Node.prototype.getMeta.call(this);

    // 增加Graphics需要序列化的内容
    return json;
};

/*
 * Draws a single {qc.Polygon} triangle from a {qc.Point} array
 *
 * @method qc.Graphics.prototype.drawTriangle
 * @param {Array<qc.Point>} points - An array of qc.Points that make up the three vertices of this triangle
 * @param {boolean} [cull=false] - Should we check if the triangle is back-facing
 */
Graphics.prototype.drawTriangle = function(points, cull) {
    return this.phaser.drawTriangle(points, cull);
};

/*
 * Draws {qc.Polygon} triangles
 *
 * @method qc.Graphics.prototype.drawTriangles
 * @param {Array<qc.Point>|Array<number>} vertices - An array of qc.Points or numbers that make up the vertices of the triangles
 * @param {Array<number>} {indices=null} - An array of numbers that describe what order to draw the vertices in
 * @param {boolean} [cull=false] - Should we check if the triangle is back-facing
 */
Graphics.prototype.drawTriangles = function(vertices, indices, cull) {
    return this.phaser.drawTriangles(vertices, indices, cull);
};

/**
 * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method.
 *
 * @method lineStyle
 * @param lineWidth {Number} width of the line to draw, will update the objects stored style
 * @param color {Number} color of the line to draw, will update the objects stored style
 * @param alpha {Number} alpha of the line to draw, will update the objects stored style
 * @return {Graphics}
 */
Graphics.prototype.lineStyle = function(lineWidth, color, alpha) {
    return this.phaser.lineStyle(lineWidth, color, alpha);
};

/**
 * Moves the current drawing position to x, y.
 *
 * @method moveTo
 * @param x {Number} the X coordinate to move to
 * @param y {Number} the Y coordinate to move to
 * @return {Graphics}
 */
Graphics.prototype.moveTo = function(x, y) {
    return this.phaser.moveTo(x, y);
};

/**
 * Draws a line using the current line style from the current drawing position to (x, y);
 * The current drawing position is then set to (x, y).
 *
 * @method lineTo
 * @param x {Number} the X coordinate to draw to
 * @param y {Number} the Y coordinate to draw to
 * @return {Graphics}
 */
Graphics.prototype.lineTo = function(x, y) {
    return this.phaser.lineTo(x, y);
};

/**
 * Calculate the points for a quadratic bezier curve and then draws it.
 * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c
 *
 * @method quadraticCurveTo
 * @param cpX {Number} Control point x
 * @param cpY {Number} Control point y
 * @param toX {Number} Destination point x
 * @param toY {Number} Destination point y
 * @return {Graphics}
 */
Graphics.prototype.quadraticCurveTo = function(cpX, cpY, toX, toY) {
    return this.quadraticCurveTo(cpX, cpY, toX, toY);
};

/**
 * Calculate the points for a bezier curve and then draws it.
 *
 * @method bezierCurveTo
 * @param cpX {Number} Control point x
 * @param cpY {Number} Control point y
 * @param cpX2 {Number} Second Control point x
 * @param cpY2 {Number} Second Control point y
 * @param toX {Number} Destination point x
 * @param toY {Number} Destination point y
 * @return {Graphics}
 */
Graphics.prototype.bezierCurveTo = function(cpX, cpY, cpX2, cpY2, toX, toY) {
    return this.phaser.bezierCurveTo(cpX, cpY, cpX2, cpY2, toX, toY);
};

/*
 * The arcTo() method creates an arc/curve between two tangents on the canvas.
 *
 * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google!
 *
 * @method arcTo
 * @param x1 {Number} The x-coordinate of the beginning of the arc
 * @param y1 {Number} The y-coordinate of the beginning of the arc
 * @param x2 {Number} The x-coordinate of the end of the arc
 * @param y2 {Number} The y-coordinate of the end of the arc
 * @param radius {Number} The radius of the arc
 * @return {Graphics}
 */
Graphics.prototype.arcTo = function(x1, y1, x2, y2, radius) {
    return this.phaser.arcTo(x1, y1, x2, y2, radius);
};

/**
 * The arc method creates an arc/curve (used to create circles, or parts of circles).
 *
 * @method arc
 * @param cx {Number} The x-coordinate of the center of the circle
 * @param cy {Number} The y-coordinate of the center of the circle
 * @param radius {Number} The radius of the circle
 * @param startAngle {Number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle)
 * @param endAngle {Number} The ending angle, in radians
 * @param anticlockwise {Boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise.
 * @return {Graphics}
 */
Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) {
    return this.phaser.arc(cx, cy, radius, startAngle, endAngle, anticlockwise);
};

/**
 * Specifies a simple one-color fill that subsequent calls to other Graphics methods
 * (such as lineTo() or drawCircle()) use when drawing.
 *
 * @method beginFill
 * @param color {Number} the color of the fill
 * @param alpha {Number} the alpha of the fill
 * @return {Graphics}
 */
Graphics.prototype.beginFill = function(color, alpha) {
    return this.phaser.beginFill(color, alpha);
};

/**
 * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method.
 *
 * @method endFill
 * @return {Graphics}
 */
Graphics.prototype.endFill = function() {
    return this.phaser.endFill();
};

/**
 * @method drawRect
 *
 * @param x {Number} The X coord of the top-left of the rectangle
 * @param y {Number} The Y coord of the top-left of the rectangle
 * @param width {Number} The width of the rectangle
 * @param height {Number} The height of the rectangle
 * @return {Graphics}
 */
Graphics.prototype.drawRect = function(x, y, width, height) {
    return this.phaser.drawRect(x, y, width, height);
};

/**
 * @method drawRoundedRect
 *
 * @param x {Number} The X coord of the top-left of the rectangle
 * @param y {Number} The Y coord of the top-left of the rectangle
 * @param width {Number} The width of the rectangle
 * @param height {Number} The height of the rectangle
 * @param radius {Number} Radius of the rectangle corners
 */
Graphics.prototype.drawRoundedRect = function(x, y, width, height, radius) {
    return this.phaser.drawRoundedRect(x, y, width, height, radius);
};

/*
 * Draws a circle.
 *
 * @method Phaser.Graphics.prototype.drawCircle
 * @param {Number} x - The X coordinate of the center of the circle.
 * @param {Number} y - The Y coordinate of the center of the circle.
 * @param {Number} diameter - The diameter of the circle.
 * @return {Graphics} This Graphics object.
 */
Graphics.prototype.drawCircle = function(x, y, diameter) {
    return this.phaser.drawCircle(x, y, diameter);
};

/**
 * Draws an ellipse.
 *
 * @method drawEllipse
 * @param x {Number} The X coordinate of the center of the ellipse
 * @param y {Number} The Y coordinate of the center of the ellipse
 * @param width {Number} The half width of the ellipse
 * @param height {Number} The half height of the ellipse
 * @return {Graphics}
 */
Graphics.prototype.drawEllipse = function(x, y, width, height) {
    return this.phaser.drawEllipse(x, y, width, height);
};

/**
 * Draws a polygon using the given path.
 *
 * @method drawPolygon
 * @param path {Array} The path data used to construct the polygon. If you've got a qc.Polygon object then pass `polygon.points` here.
 * @return {Graphics}
 */
Graphics.prototype.drawPolygon = function(path) {
    return this.phaser.drawPolygon(path);
};

/**
 * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings.
 *
 * @method clear
 * @return {Graphics}
 */
Graphics.prototype.clear = function() {
    return this.phaser.clear();
};

/**
 * Useful function that returns a texture of the graphics object that can then be used to create sprites
 * This can be quite useful if your geometry is complicated and needs to be reused multiple times.
 *
 * @method generateTexture
 * @param resolution {Number} The resolution of the texture being generated
 * @param scaleMode {Number} Should be one of the PIXI.scaleMode consts
 * @return {Texture} a texture of the graphics object
 */
Graphics.prototype.generateTexture = function(resolution, scaleMode) {
    return this.phaser.generateTexture(resolution, scaleMode);
};

/**
 * Tests if a point is inside this graphics object
 *
 * @param point {Point} the point to test
 * @return {boolean} the result of the test
 */
Graphics.prototype.containsPoint = function(point) {
    return this.phaser.containsPoint(point);
};

/**
 * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon.
 *
 * @method drawShape
 * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw.
 * @return {GraphicsData} The generated GraphicsData object.
 */
Graphics.prototype.drawShape = function(shape) {
    return this.phaser.drawShape(shape);
};
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 为节点提供边界信息编辑功能
 * @class qc.Bounds
 */
var Bounds = defineBehaviour('qc.Bounds', qc.Behaviour, function() {
    // 设置默认尺寸提供者
    this._sizeProvider = Bounds.USE_BOUNDS;

    // 是否自动每帧计算边界
    this._autoCalcBounds = true;

    // 设置默认边距
    this._marginTop = this._marginBottom = this._marginLeft = this._marginRight = 0;
    this._paddingTop = this._paddingBottom = this._paddingLeft = this._paddingRight = 0;

    // 边界遍历深度
    this._deep = 1;
},{
    _sizeProvider : qc.Serializer.NUMBER,
    _marginTop : qc.Serializer.NUMBER,
    _marginBottom : qc.Serializer.NUMBER,
    _marginLeft : qc.Serializer.NUMBER,
    _marginRight : qc.Serializer.NUMBER,
    _paddingTop : qc.Serializer.NUMBER,
    _paddingBottom : qc.Serializer.NUMBER,
    _paddingLeft : qc.Serializer.NUMBER,
    _paddingRight : qc.Serializer.NUMBER,
    _autoCalcBounds : qc.Serializer.BOOLEAN
});

// 菜单上的隐藏
Bounds.__hiddenInMenu = true;

Object.defineProperties(Bounds.prototype,{
    /**
     * @property {Number} sizeProvider - 尺寸提供方式
     */
    sizeProvider : {
        get : function() { return this._sizeProvider; },
        set : function(value) {
            if (this._sizeProvider === value) {
                return;
            }
            this._sizeProvider = value;
            this.getBounds(true);
        }
    },

    /**
     * @property {Number} marginTop - 相对父控件上边的边距
     */
    marginTop : {
        get : function() { return this._marginTop; },
        set : function(value) {
            this.setMargin(value);
        }
    },

    /**
     * @property {Number} marginBottom - 相对父控件下边的边距
     */
    marginBottom : {
        get : function() { return this._marginBottom; },
        set : function(value) {
            this.setMargin(null, null, value);
        }
    },

    /**
     * @property {Number} marginLeft - 相对父控件左边的边距
     */
    marginLeft : {
        get : function() { return this._marginLeft; },
        set : function(value) {
            this.setMargin(null, null, null, value);
        }
    },

    /**
     * @property {Number} marginRight - 相对父控件右边的边距
     */
    marginRight : {
        get : function() { return this._marginRight; },
        set : function(value) {
            this.setMargin(null, value);
        }
    },

    /**
     * @property {Number} paddingTop - 相对子控件上边的边距
     */
    paddingTop : {
        get : function() { return this._paddingTop; },
        set : function(value) {
            this.setPadding(value);
        }
    },

    /**
     * @property {Number} paddingBottom - 相对子控件下边的边距
     */
    paddingBottom : {
        get : function() { return this._paddingBottom; },
        set : function(value) {
            this.setPadding(null, null, value);
        }
    },

    /**
     * @property {Number} paddingLeft - 相对子控件左边的边距
     */
    paddingLeft : {
        get : function() { return this._paddingLeft; },
        set : function(value) {
            this.setPadding(null, null, null, value);
        }
    },

    /**
     * @property {Number} paddingRight - 相对子控件右边的边距
     */
    paddingRight : {
        get : function() { return this._paddingRight; },
        set : function(value) {
            this.setPadding(null, value);
        }
    },

    autoCalcBounds : {
        get : function() { return this._autoCalcBounds; },
        set : function(value) {
            this._autoCalcBounds = value;
        }
    }
});

/**
 * 设置内边距
 * @param top
 * @param right
 * @param bottom
 * @param left
 */
Bounds.prototype.setPadding = function(top, right, bottom, left) {
    typeof top !== 'undefined' && top !== null && (this._paddingTop = top);
    typeof right !== 'undefined' && right !== null && (this._paddingRight = right);
    typeof bottom !== 'undefined' && bottom !== null && (this._paddingBottom = bottom);
    typeof left !== 'undefined' && left !== null && (this._paddingLeft = left);
    this._lastWrapTime = null;
    if (this.relayout) {
        this.relayout();
    }
    this.gameObject && this.gameObject._dispatchLayoutArgumentChanged('layout');
};

/**
 * 设置外边距
 * @param top
 * @param right
 * @param bottom
 * @param left
 */
Bounds.prototype.setMargin = function(top, right, bottom, left) {
    typeof top !== 'undefined' && top !== null && (this._marginTop = top);
    typeof right !== 'undefined' && right !== null && (this._marginRight = right);
    typeof bottom !== 'undefined' && bottom !== null && (this._marginBottom = bottom);
    typeof left !== 'undefined' && left !== null && (this._marginLeft = left);
    this._lastWrapTime = null;
    if (this.relayout) {
        this.relayout();
    }
    this.gameObject && this.gameObject._dispatchLayoutArgumentChanged('layout');
};

/**
 * 重新计算边界
 */
Bounds.prototype.calcBounds = function() {
    this.getBounds(true);
};

/**
 * 得到边界
 * @param force {boolean} - 是否强制重算
 */
Bounds.prototype.getBounds = function(force) {
    var rect = this._recordBounds || new qc.Rectangle(0, 0, 0, 0);
    if (!this.enable || !this.gameObject || !this.gameObject.isWorldVisible()) {
        return rect;
    }
    if (this._recordBounds && !force &&
        (!this.autoCalcBounds || this._lastWrapTime === this.gameObject.game.time.fixedTime)) {
        return this._recordBounds;
    }
    this._lastWrapTime = this.gameObject.game.time.fixedTime;
    var minPos = new qc.Point(0, 0);
    var maxPos = new qc.Point(0, 0);
    Bounds._calcBounds(minPos, maxPos, this.sizeProvider, this.gameObject, this._deep);
    rect.x = minPos.x;
    rect.y = minPos.y;
    rect.width = maxPos.x - minPos.x;
    rect.height = maxPos.y - minPos.y;
    this._recordBounds = rect;
    return rect;
};

/**
 * 得到边界在世界坐标系中的四个顶点
 * @param force {boolean} - 是否强制重算
 */
Bounds.prototype.getWorldCorners = function(force) {
    var rect = this.getBounds(force);
    var leftTop = new qc.Point(rect.x, rect.y);
    var rightTop = new qc.Point(rect.x + rect.width, rect.y);
    var rightBottom = new qc.Point(rect.x + rect.width, rect.y + rect.height);
    var leftBottom = new qc.Point(rect.x, rect.y + rect.height);

    var worldTransform = this.gameObject.worldTransform;

    return [worldTransform.apply(leftTop),
        worldTransform.apply(rightTop),
        worldTransform.apply(rightBottom),
        worldTransform.apply(leftBottom)];
};

/**
 * 计算节点的区间
 * @param outMin {qc.Point} - 输入已有的最小值，并返回
 * @param outMax {qc.Point} - 输入已有的最大值，并返回
 * @param sizeProvider {number} - 尺寸提供类型
 * @param node {qc.Node} - 节点
 * @param deep {Number} - 当前还需计算的深度，为0时，不计算node节点的子节点
 * @private
 */
Bounds._calcBounds = function(outMin, outMax, sizeProvider, node, deep) {
    var nodeBoundsScript = node.getScript('qc.Bounds');
    sizeProvider = nodeBoundsScript && nodeBoundsScript.enable ? nodeBoundsScript.sizeProvider : sizeProvider;
    if (sizeProvider === Bounds.USE_BOUNDS) {
        this._calcRealBounds(outMin, outMax, node, deep);
    }
    else {
        this._calcRectBounds(outMin, outMax, node, deep);
    }
};

/**
 * 计算节点的RectTransform区间
 * @param outMin {qc.Point} - 输入已有的最小值，并返回
 * @param outMax {qc.Point} - 输入已有的最大值，并返回
 * @param node {qc.Node} - 节点
 * @param deep {Number} - 当前还需计算的深度，为0时，不计算node节点的子节点
 * @private
 */
Bounds._calcRectBounds = function(outMin, outMax, node, deep) {
    var nodeBoundsScript = node.getScript('qc.Bounds');
    // 计算自己的边界
    var rect = node.rect;
    var minX = rect.x, minY = rect.y, maxX = rect.x + rect.width, maxY = rect.y + rect.height;


    // 计算子节点的边界
    if (deep !== 0) {
        var children = node.children;
        var len = children.length;
        while (len--) {
            var child = children[len];
            if (!child.isWorldVisible()) {
                continue;
            }
            // 如果当前层级不是0，则继续向下计算
            var childBoundsScript = child.getScript('qc.Bounds');
            var minPos = new qc.Point(0, 0);
            var maxPos = new qc.Point(0, 0);
            if (childBoundsScript) {
                var childBounds = childBoundsScript.getBounds();
                minPos.x = childBounds.x;
                minPos.y = childBounds.y;
                maxPos.x = childBounds.x + childBounds.width;
                maxPos.y = childBounds.y + childBounds.height;
            }
            else {
                // 计算子节点边界信息
                Bounds._calcRectBounds(minPos, maxPos, child, deep - 1);
            }
            if (nodeBoundsScript && nodeBoundsScript.enable) {
                // 为子节点加上内边距
                minPos.x -= nodeBoundsScript.paddingLeft;
                minPos.y -= nodeBoundsScript.paddingTop;
                maxPos.x += nodeBoundsScript.paddingRight;
                maxPos.y += nodeBoundsScript.paddingBottom;
            }

            // 调整节点边界
            minX = Math.min(minX, minPos.x);
            minY = Math.min(minY, minPos.y);
            maxX = Math.max(maxX, maxPos.x);
            maxY = Math.max(maxY, maxPos.y);
        }
    }

    if (nodeBoundsScript && nodeBoundsScript.enable) {
        // 加上外边距
        minX -= nodeBoundsScript.marginLeft;
        minY -= nodeBoundsScript.marginTop;
        maxX += nodeBoundsScript.marginRight;
        maxY += nodeBoundsScript.marginBottom;
    }
    // 更新输出边界
    outMin.x = Math.min(outMin.x, minX);
    outMin.y = Math.min(outMin.y, minY);
    outMax.x = Math.max(outMax.x, maxX);
    outMax.y = Math.max(outMax.y, maxY);
};

/**
 * 计算节点的包围区间
 * @param outMin {qc.Point} - 输入已有的最小值，并返回
 * @param outMax {qc.Point} - 输入已有的最大值，并返回
 * @param node {qc.Node} - 节点
 * @param deep {Number} - 当前还需计算的深度，为0时，不计算node节点的子节点
 * @private
 */
Bounds._calcRealBounds = function(outMin, outMax, node, deep) {
    var nodeBoundsScript = node.getScript('qc.Bounds');
    // 计算自己的边界
    var rect = node.rect;
    var minX = rect.x, minY = rect.y, maxX = rect.x + rect.width, maxY = rect.y + rect.height;

    // 计算子节点的边界
    if (deep !== 0) {
        var nodeTransform = node.worldTransform;
        var children = node.children;
        var len = children.length;
        while (len--) {
            var child = children[len];
            if (!child.isWorldVisible()) {
                continue;
            }
            // 如果当前层级不是0，则继续向下计算
            var childBoundsScript = child.getScript('qc.Bounds');
            var minPos = new qc.Point(0, 0);
            var maxPos = new qc.Point(0, 0);
            if (childBoundsScript) {
                var childBounds = childBoundsScript.getBounds();
                minPos.x = childBounds.x;
                minPos.y = childBounds.y;
                maxPos.x = childBounds.x + childBounds.width;
                maxPos.y = childBounds.y + childBounds.height;
            }
            else {
                // 计算子节点边界信息
                Bounds._calcRealBounds(minPos, maxPos, child, deep - 1);
            }
            var leftTop = new qc.Point(minPos.x, minPos.y);
            var rightTop = new qc.Point(maxPos.x, minPos.y);
            var rightBottom = new qc.Point(maxPos.x, maxPos.y);
            var leftBottom = new qc.Point(minPos.x, maxPos.y);
            var childTransform = child.worldTransform;
            leftTop = nodeTransform.applyInverse(childTransform.apply(leftTop));
            rightTop = nodeTransform.applyInverse(childTransform.apply(rightTop));
            rightBottom = nodeTransform.applyInverse(childTransform.apply(rightBottom));
            leftBottom = nodeTransform.applyInverse(childTransform.apply(leftBottom));

            var childMinX = Math.min(leftTop.x, rightTop.x, rightBottom.x, leftBottom.x);
            var childMinY = Math.min(leftTop.y, rightTop.y, rightBottom.y, leftBottom.y);
            var childMaxX = Math.max(leftTop.x, rightTop.x, rightBottom.x, leftBottom.x);
            var childMaxY = Math.max(leftTop.y, rightTop.y, rightBottom.y, leftBottom.y);

            if (nodeBoundsScript && nodeBoundsScript.enable) {
                // 为子节点加上内边距
                childMinX -= nodeBoundsScript.paddingLeft;
                childMinY -= nodeBoundsScript.paddingTop;
                childMaxX += nodeBoundsScript.paddingRight;
                childMaxY += nodeBoundsScript.paddingBottom;
            }

            // 调整节点边界
            minX = Math.min(minX, childMinX);
            minY = Math.min(minY, childMinY);
            maxX = Math.max(maxX, childMaxX);
            maxY = Math.max(maxY, childMaxY);
        }
    }

    if (nodeBoundsScript && nodeBoundsScript.enable) {
        // 加上外边距
        minX -= nodeBoundsScript.marginLeft;
        minY -= nodeBoundsScript.marginTop;
        maxX += nodeBoundsScript.marginRight;
        maxY += nodeBoundsScript.marginBottom;
    }
    // 更新输出边界
    outMin.x = Math.min(outMin.x, minX);
    outMin.y = Math.min(outMin.y, minY);
    outMax.x = Math.max(outMax.x, maxX);
    outMax.y = Math.max(outMax.y, maxY);
};

/**
 * 获取一个节点在自身坐标系下，指定深度内的边界信息
 * @param node {qc.Node} - 节点
 * @param sizeProvider {Number} - 尺寸提供类型
 * @param force {boolean} - 是否强制重算，默认不强制
 * @param deep {Number} - 遍历深度，默认为0，只计算自己本节点大小
 */
Bounds.getBounds = function(node, sizeProvider, force, deep) {
    deep = (typeof deep === 'undefined' || deep === null) ? 0 : deep;
    var nodeBoundsScript = node.getScript('qc.Bounds');
    if (nodeBoundsScript && nodeBoundsScript.enable) {
        return nodeBoundsScript.getBounds(force);
    }
    var minPos = new qc.Point(0, 0);
    var maxPos = new qc.Point(0, 0);
    Bounds._calcBounds(minPos, maxPos, sizeProvider, node, deep);
    var rect = new qc.Rectangle(0, 0, 0, 0);
    rect.x = minPos.x;
    rect.y = minPos.y;
    rect.width = maxPos.x - minPos.x;
    rect.height = maxPos.y - minPos.y;
    return rect;
};

/**
 * 获取一个节点下指定深度内边界顶点在指定对象坐标系信息
 * @param node {qc.Node} - 节点
 * @param force {boolean} - 是否强制重算，默认不强制
 * @param deep {Number} - 遍历深度，默认为0，只计算自己本节点大小
 * @param target {qc.Node} - 目标坐标系
 */
Bounds.getCorners = function(node, sizeProvider, force, deep, target) {
    var rect = Bounds.getBounds(node, sizeProvider, force, deep);
    var worldTransform = node.worldTransform;
    var targetTransform = target && target.worldTransform;
    if (sizeProvider === Bounds.USE_RECTTRANSFORM) {
        var root = Bounds.findCommonRoot(node, target || node.game.world);
        var offX = 0;
        var offY = 0;
        var tmp = node;
        while (tmp && tmp !== node.game.world && tmp !== root) {
            offX += tmp.x;
            offY += tmp.y;
            tmp = node.parent;
        }
        tmp = target;
        while (tmp && tmp !== node.game.world && tmp !== root) {
            offX -= tmp.x;
            offY -= tmp.y;
        }
        var leftTop = new qc.Point(rect.x + offX, rect.y + offY);
        var rightTop = new qc.Point(rect.x + rect.width + offX, rect.y + offY);
        var rightBottom = new qc.Point(rect.x + rect.width + offX, rect.y + rect.height + offY);
        var leftBottom = new qc.Point(rect.x + offX, rect.y + rect.height + offY);
        return [leftTop, rightTop, rightBottom, leftBottom];
    }
    else {
        var leftTop = new qc.Point(rect.x, rect.y);
        var rightTop = new qc.Point(rect.x + rect.width, rect.y);
        var rightBottom = new qc.Point(rect.x + rect.width, rect.y + rect.height);
        var leftBottom = new qc.Point(rect.x, rect.y + rect.height);
        return (!targetTransform) ?
            [worldTransform.apply(leftTop), worldTransform.apply(rightTop),
                worldTransform.apply(rightBottom), worldTransform.apply(leftBottom)] :
            [targetTransform.applyInverse(worldTransform.apply(leftTop)),
                targetTransform.applyInverse(worldTransform.apply(rightTop)),
                targetTransform.applyInverse(worldTransform.apply(rightBottom)),
                targetTransform.applyInverse(worldTransform.apply(leftBottom))];
    }
};

/**
 * 获取一个节点指定深度内的边界，在指定对象坐标系中的包围盒信息
 * @param node {qc.Node} - 节点
 * @param force {boolean} - 是否强制重算，默认不强制
 * @param deep {Number} - 遍历深度，默认为0，只计算自己本节点大小
 * @param target {qc.Node} - 目标坐标系
 */
Bounds.getBox = function(node, sizeProvider, force, deep, target) {
    var corners = Bounds.getCorners(node, sizeProvider, force, deep, target);
    var maxPos = new qc.Point(-Number.MAX_VALUE, -Number.MAX_VALUE);
    var minPos = new qc.Point(Number.MAX_VALUE, Number.MAX_VALUE);
    for (var i = 0; i < 4; i++) {
        var one = corners[i];
        maxPos.x = Math.max(maxPos.x, one.x);
        maxPos.y = Math.max(maxPos.y, one.y);
        minPos.x = Math.min(minPos.x, one.x);
        minPos.y = Math.min(minPos.y, one.y);
    }
    return new qc.Rectangle(minPos.x, minPos.y, maxPos.x - minPos.x, maxPos.y - minPos.y);
};

/**
 * 获取内容区域大小
 * @param node {qc.Node}
 */
Bounds.getContentRect = function(node) {
    var rect = node.rect;
    var nodeBoundsScript = node.getScript('qc.Bounds');
    if (nodeBoundsScript && nodeBoundsScript.enable) {
        rect.x += nodeBoundsScript.paddingLeft;
        rect.y += nodeBoundsScript.paddingTop;
        rect.width -= nodeBoundsScript.paddingLeft + nodeBoundsScript.paddingRight;
        rect.height -= nodeBoundsScript.paddingTop + nodeBoundsScript.paddingBottom;
    }
    return rect;
};

/**
 * 获取外边距
 * @return {{}}}
 */
Bounds.getMargin = function(node) {
    var script = node.getScript('qc.Bounds');
    if (script && script.enable) {
        return {
            top: script._marginTop,
            bottom: script._marginBottom,
            left: script._marginLeft,
            right:script._marginRight
        };
    }
    else {
        return {
            top: 0,
            bottom: 0,
            left: 0,
            right: 0
        };
    }
};

/**
 * 查找两个节点的共同的节点
 * @param one {qc.Node} - 节点
 * @param two {qc.Node} - 节点
 */
Bounds.findCommonRoot = function(one, two) {
    if (!one || !two) {
        return null;
    }
    var t1 = one;
    var t2 = two;
    while (t1) {
        t2 = two;
        while (t2) {
            if (t1 === t2) {
                return t1;
            }
            t2 = t2.parent;
        }
        t1 = t1.parent;
    }
    return null;
};

/**
 * 使用实际显示范围作为边框
 * @constant
 * @type {number}
 */
Bounds.USE_BOUNDS = 0;
/**
 * 使用 rectTransform 作为边框
 * @constant
 * @type {number}
 */
Bounds.USE_RECTTRANSFORM = 1;
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 布局器使用的节点
 * 用来控制节点在布局管理器中的表现
 * 尺寸分为：最小值，首选值，可变值，最大值。
 * 不设置时表示没有限制
 * 满足规则如下：
 * - 如果不能满足最小值的设定，则以最小值设置
 * - 如果不能或者刚好满足首选值的设定，则以首选值为权重进行分配空间
 * - 如果空间不大于最大值的设定，则将满足首选值外的剩余空间以可变值为权重进行分配
 * - 如果大于最大值的设定，则按最大值设置
 * @class qc.LayoutElement
 */
var LayoutElement = defineBehaviour('qc.LayoutElement', qc.Bounds, function() {
    /**
     * @property {boolean} ignoreLayout - 是否允许忽略布局参数
     */
    this.ignoreLayout = false;
    /**
     * @property {Number} minWidth - 节点允许的最小宽度
     */
    this.minWidth = -1;
    /**
     * @property {Number} minHeight - 节点允许的最小高度
     */
    this.minHeight = -1;
    /**
     * @property {Number} preferredWidth - 首选宽度
     */
    this.preferredWidth = -1;
    /**
     * @property {Number} preferredWidth - 首选高度
     */
    this.preferredHeight = -1;
    /**
     * @property {Number} flexibleWidth - 可变宽度
     */
    this.flexibleWidth = -1;
    /**
     * @property {Number} flexibleWidth - 可变高度
     */
    this.flexibleHeight = -1;

    /**
     * @property {number} _deep - 边界遍历深度
     */
    this._deep = 0;
},{
    ignoreLayout : qc.Serializer.BOOLEAN,
    minWidth : qc.Serializer.NUMBER,
    minHeight : qc.Serializer.NUMBER,
    preferredWidth : qc.Serializer.NUMBER,
    preferredHeight : qc.Serializer.NUMBER,
    flexibleWidth : qc.Serializer.NUMBER,
    flexibleHeight : qc.Serializer.NUMBER
});

// 菜单上的显示
LayoutElement.__menu = 'UI/Layout/LayoutElement';

Object.defineProperties(LayoutElement.prototype, {
    /**
     * @property {boolean} ignoreLayout - 是否允许忽略布局参数
     */
    ignoreLayout : {
        get : function() { return this._ignoreLayout; },
        set : function(v) {
            if (v === this._ignoreLayout) return;
            this._ignoreLayout = v;
        }
    },
    /**
     * @property {Number} minWidth - 节点允许的最小宽度
     */
    minWidth : {
        get : function() { return this._minWidth; },
        set : function(v) {
            if (v === this._minWidth) return;
            this._minWidth = v;
            this.gameObject && this.gameObject._dispatchLayoutArgumentChanged('layout');
        }
    },
    /**
     * @property {Number} minHeight - 节点允许的最小高度
     */
    minHeight : {
        get : function() { return this._minHeight; },
        set : function(v) {
            if (v === this._minHeight) return;
            this._minHeight = v;
            this.gameObject && this.gameObject._dispatchLayoutArgumentChanged('layout');
        }
    },
    /**
     * @property {Number} preferredWidth - 首选宽度
     */
    preferredWidth : {
        get : function() { return this._preferredWidth; },
        set : function(v) {
            if (v === this._preferredWidth) return;
            this._preferredWidth = v;
            this.gameObject && this.gameObject._dispatchLayoutArgumentChanged('layout');
        }
    },
    /**
     * @property {Number} preferredWidth - 首选高度
     */
    preferredHeight : {
        get : function() { return this._preferredHeight; },
        set : function(v) {
            if (v === this._preferredHeight) return;
            this._preferredHeight = v;
            this.gameObject && this.gameObject._dispatchLayoutArgumentChanged('layout');
        }
    },
    /**
     * @property {Number} flexibleWidth - 可变宽度
     */
    flexibleWidth : {
        get : function() { return this._flexibleWidth; },
        set : function(v) {
            if (v === this._flexibleWidth) return;
            this._flexibleWidth = v;
            this.gameObject && this.gameObject._dispatchLayoutArgumentChanged('layout');
        }
    },
    /**
     * @property {Number} flexibleWidth - 可变高度
     */
    flexibleHeight : {
        get : function() { return this._flexibleHeight; },
        set : function(v) {
            if (v === this._flexibleHeight) return;
            this._flexibleHeight = v;
            this.gameObject && this.gameObject._dispatchLayoutArgumentChanged('layout');
        }
    },

    /**
     * @property {boolean} hasMinWidth - 是否设置了最小宽度
     */
    hasMinWidth : {
        get : function() { return this.minWidth >= 0; },
        set : function(v) {
            if (this.hasMinWidth === v) {
                return;
            }
            this.minWidth = v ? 0 : -1;
        }
    },
    /**
     * @property {boolean} hasMinHeight - 是否设置了最小高度
     */
    hasMinHeight : {
        get : function() { return this.minHeight >= 0; },
        set : function(v) {
            if (this.hasMinHeight === v) {
                return;
            }
            this.minHeight = v ? 0 : -1;
        }
    },
    /**
     * @property {boolean} hasPreferredWidth - 是否设置了首选宽度
     */
    hasPreferredWidth : {
        get : function() { return this.preferredWidth >= 0; },
        set : function(v) {
            if (this.hasPreferredWidth === v) {
                return;
            }
            this.preferredWidth = !v ? -1 : (this.gameObject ? this.gameObject.width : 0);
        }
    },
    /**
     * @property {boolean} hasMinHeight - 是否设置了首选高度
     */
    hasPreferredHeight : {
        get : function() { return this.preferredHeight >= 0; },
        set : function(v) {
            if (this.hasPreferredHeight === v) {
                return;
            }
            this.preferredHeight = !v ? -1 : (this.gameObject ? this.gameObject.height : 0);
        }
    },
    /**
     * @property {boolean} hasFlexibleWidth - 是否设置了拓展宽度
     */
    hasFlexibleWidth : {
        get : function() { return this.flexibleWidth >= 0; },
        set : function(v) {
            if (this.hasFlexibleWidth === v) {
                return;
            }
            this.flexibleWidth = v ? 1 : -1;
        }
    },
    /**
     * @property {boolean} hasMinHeight - 是否设置了首选高度
     */
    hasFlexibleHeight : {
        get : function() { return this.flexibleHeight >= 0; },
        set : function(v) {
            if (this.hasFlexibleHeight === v) {
                return;
            }
            this.flexibleHeight = v ? 1 : -1;
        }
    }
});

/**
 * 获取一个节点上的布局元素
 * @param node
 * @returns {*|LayoutElement}
 */
LayoutElement.getLayoutElement = function(node) {
    var ret = new LayoutElement();
    var element = node.getScript('qc.LayoutElement');
    if (element && !element.ignoreLayout) {
        ret.ignoreLayout = element.ignoreLayout;
        ret.minWidth = element.minWidth;
        ret.minHeight = element.minHeight;
        ret.preferredWidth = element.preferredWidth;
        ret.preferredHeight = element.preferredHeight;
        ret.flexibleWidth = element.flexibleWidth;
        ret.flexibleHeight = element.flexibleHeight;
    }
    return ret;
};

/**
 * 使用 RectTransform 作为布局尺寸提供者
 * RectTransform不受 Scale，Rotation 参数影响
 * @constant
 * @type {number}
 */
LayoutElement.USE_RECTTRANSFORM = qc.Bounds.USE_RECTTRANSFORM;

/**
 * 使用Bounds作为布局尺寸提供者
 * Bounds为实现显示节点需要的尺寸
 * @constant
 * @type {number}
 */
LayoutElement.USE_BOUNDS = qc.Bounds.USE_BOUNDS;
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 表格布局器，元素加入规则为，将所有元素按子节点顺序
 * @class qc.TableLayout
 */
var TableLayout = defineBehaviour('qc.TableLayout', qc.Bounds, function() {
    /**
     * @property [[[{}]]] _cellStyle - 每个单元格样式信息，单元格信息从(1,1)开始存储
     * @private
     */
    this._cellStyle = [[this._buildCellInfo()]];
    /**
     * @property {number} _constraint - 表格限制
     * @private
     */
    this._constraint = TableLayout.CONSTRAINT_FLEXIBLE;

    // 在编辑器模式下需要运行
    this.runInEditor = true;

    // 默认单元格的对其方式
    this.cellAlignment = TableLayout.ALIGN_TOP_LEFT;


    this.gameObject.onChildrenChanged.add(this._doChildrenChanged, this);
    this.gameObject.onLayoutArgumentChanged.add(this._doLayoutArgumentChanged, this);
},{
    constraint : qc.Serializer.NUMBER,
    stride : qc.Serializer.NUMBER,
    startCorner : qc.Serializer.NUMBER,
    startAxis : qc.Serializer.NUMBER,
    contentSizeProvider : qc.Serializer.NUMBER,
    style : qc.Serializer.NUMBER,
    _cellStyle : qc.Serializer.MAPPING,
    autoUpdate : qc.Serializer.BOOLEAN,
    contentAlignment : qc.Serializer.NUMBER,
    cellAlignment : qc.Serializer.NUMBER,
    spacingX : qc.Serializer.NUMBER,
    spacingY : qc.Serializer.NUMBER,
    ignoreX : qc.Serializer.BOOLEAN,
    ignoreY : qc.Serializer.BOOLEAN
});

// 菜单上的显示
TableLayout.__menu = 'UI/Layout/TableLayout';

TableLayout._precision = 0.0000001;

Object.defineProperties(TableLayout.prototype, {
    constraint : {
        get : function() { return isNaN(this._constraint) ? TableLayout.CONSTRAINT_FLEXIBLE : this._constraint; },
        set : function(value) {
            if (this._constraint === value) {
                return;
            }
            this._constraint = value;
            this.rebuildTable();
        }
    },

    /**
     * @prototype {Number} stride - 步幅，开始方向为水平方向时，为列数，开始方向为垂直方向时，为行数
     */
    stride : {
        get : function() { return isNaN(this._stride) ? 0 : this._stride; },
        set : function(value) {
            if (value === this._stride) {
                return;
            }
            this._stride = value;
            this.rebuildTable();
        }
    },

    /**
     * @prototype {number} startCorner - 布局开始的角，默认从左上开始
     */
    startCorner : {
        get : function() { return this._startCorner || TableLayout.CORNER_TOP_LEFT; },
        set : function(value) {
            if (value === this._startCorner) {
                return;
            }
            this._startCorner = value;
            this.rebuildTable();
        }
    },

    /**
     * @prototype {number} startAxis - 布局开始添加的轴，默认优先填充水平方向
     */
    startAxis : {
        get : function() { return this._startAxis || TableLayout.AXIS_HORIZONTAL; },
        set : function(value) {
            if (value === this._startAxis) {
                return;
            }
            this._startAxis = value;
            this.rebuildTable();
        }
    },

    /**
     * @prototype {Number} contentSizeProvider - 默认使用的内容尺寸提供器，如果节点上有附加相关脚本则使用节点设置
     */
    contentSizeProvider : {
        get : function() { return this._contentSizeProvider || TableLayout.USE_BOUNDS; },
        set : function(value) {
            if (value === this._contentSizeProvider) {
                return;
            }
            this._contentSizeProvider = value;
            this.rebuildTable();
        }
    },

    /**
     * @prototype {number} style - 当前布局器的样式，是保持子节点大小还是以本节点 RectTransform 对子节点进行调整，默认保持子节点尺寸
     */
    style : {
        get : function() { return this._style || TableLayout.STYLE_WRAP_ELEMENT; },
        set : function(value) {
            if (value === this._style) {
                return;
            }
            this._style = value;
            this.rebuildTable();
        }
    },

    /**
     * @prototype {boolean} autoUpdate - 是否每帧自动重排
     */
    autoUpdate : {
        get : function() { return this._autoUpdate; },
        set : function(value) {
            this._autoUpdate = value;
        }
    },

    /**
     * @property {number} spacingX - 设置单元格的水平间距
     */
    spacingX : {
        get : function() {
            return this._spacingX || 0;
        },
        set : function(value) {
            if (this._spacingX === value) {
                return;
            }
            this._spacingX = value;
            this.rebuildTable();
        }
    },

    /**
     * @property {number} spacingY - 设置单元格的垂直间距
     */
    spacingY : {
        get : function() {
            return this._spacingY || 0;
        },
        set : function(value) {
            if (this._spacingY === value) {
                return;
            }
            this._spacingY = value;
            this.rebuildTable();
        }
    },
    /**
     * @property {number} cellWidth - 设置单元格的宽度
     */
    cellWidth : {
        get : function() {
            return this.getCellStyle(null, null).cellWidth;
        },
        set : function(value) {
            this.setCellStyle(null, null, null, null, null, null, null, value);
        }
    },

    /**
     * @property {number} cellHeight - 设置单元格的高度
     */
    cellHeight : {
        get : function() {
            return this.getCellStyle(null, null).cellHeight;
        },
        set : function(value) {
            this.setCellStyle(null, null, null, null, null, null, null, null, value);
        }
    },

    /**
     * @property {number} contentAlignment - 设置内容在容器中的对齐方式，仅在style === TableLayout.STYLE_RESIZE_ELEMENT时生效
     */
    contentAlignment : {
        get : function() {
            return this._contentAlignment || TableLayout.ALIGN_TOP_LEFT;
        },
        set : function(value) {
            if (this._contentAlignment === value) {
                return;
            }
            this._contentAlignment = value;
            this.rebuildTable();
        }
    },

    /**
     * @property {number} cellAlignment - 设置单元格的内容在容器中的对齐方式
     */
    cellAlignment : {
        get : function() {
            return this.getCellStyle(null, null).align;
        },
        set : function(value) {
            if (this.cellAlignment === value) {
                return;
            }
            this.setCellStyle(null, null, value);
            this.rebuildTable();
        }
    },

    /**
     * @property {number} columnCount - 当前列数
     */
    columnsCount : {
        get : function() {
            var size = this._calcColumnRow();
            return size.x;
        }
    },

    /**
     * @property {number} rowsCount - 当前列数
     * @readonly
     */
    rowsCount : {
        get : function() {
            var size = this._calcColumnRow();
            return size.y;
        }
    },
    /**
     * @property {boolean} ignoreX - 是否不调整 x 坐标
     */
    ignoreX : {
        get : function() { return !!this._ignoreX; },
        set : function(value) {
            if (this._ignoreX === value) return;
            this._ignoreX = value;
        }
    },
    /**
     * @property {boolean} ignoreY - 是否不调整 y 坐标
     */
    ignoreY : {
        get : function() { return !!this._ignoreY; },
        set : function(value) {
            if (this._ignoreY === value) return;
            this._ignoreY = value;
        }
    }
});

/**
 * postUpdate
 */
TableLayout.prototype.postUpdate = function() {
    // 启用状态下更新mask
    if (this.enable && (this.autoUpdate || this._needRebuildTable)) {
        this.rebuildTable();
        this._needRebuildTable = false;
    }
};

/**
 * 获取所有参与布局的子节点数量
 * @private
 */
TableLayout.prototype._getChildrenCount = function() {
    var len = 0;
    var realChildren = this.gameObject.children;
    for (var idx = 0; idx < realChildren.length; ++idx) {
        if (realChildren[idx] && realChildren[idx].isWorldVisible()) {
            len++;
        }
    }
    return len;
};

/**
 * 获取所有参与布局的子节点
 * @private
 */
TableLayout.prototype._getChildren = function() {
    var children = [];
    var realChildren = this.gameObject.children;
    for (var idx = 0; idx < realChildren.length; ++idx) {
        if (realChildren[idx] && realChildren[idx].isWorldVisible()) {
            children.push(realChildren[idx]);
        }
    }
    return children;
};

/**
 * 按规则吸收样式
 * @param base {{}} - 基础样式
 * @param append {{}} - 要吸收的样式
 * @private
 */
TableLayout.prototype._mixinStyle = function(base, append) {
    return {
        align : ((append.align & 0xF0) === 0 ? (base.align & 0xF0) : (append.align & 0xF0)) |
        ((append.align & 0x0F) === 0 ? (base.align & 0x0F) : (append.align & 0x0F)),
        timestamp : Math.max(append.timestamp, base.timestamp),
        paddingLeft : isNaN(append.paddingLeft) ? base.paddingLeft : append.paddingLeft,
        paddingRight : isNaN(append.paddingRight) ? base.paddingRight : append.paddingRight,
        paddingTop : isNaN(append.paddingTop) ? base.paddingTop : append.paddingTop,
        paddingBottom : isNaN(append.paddingBottom) ? base.paddingBottom : append.paddingBottom,
        cellWidth : isNaN(append.cellWidth) ? base.cellWidth : append.cellWidth,
        cellHeight : isNaN(append.cellHeight) ? base.cellHeight : append.cellHeight
    }
};

/**
 * 计算一个节点在父节点下的 RectTransform
 * @param node {qc.Node} - 节点
 * @param deep {number} - 遍历的深度，默认只计算节点本身
 */
TableLayout._calcRectTransform = function(node, deep) {
    deep = isNaN(deep) ? 0 : deep;
    var rect = node.rect;
    if (deep !== 0) {
        return rect;
    }
    var children = node.children;
    var len = children.length;
    while (len--) {
        var childRect = TableLayout._calcRectTransform(children[len], deep - 1);
        rect.x = Math.min(rect.x, childRect.x);
        rect.y = Math.min(rect.y, childRect.y);
        rect.w = Math.max(rect.x + rect.width, childRect.x + childRect.width) - rect.x;
        rect.h = Math.max(rect.y + rect.height, childRect.y + childRect.height) - rect.y;
    }
    return rect;
};

/**
 * 获取表格单元格的包围大小，对于已经设置了尺寸提供器的节点使用节点设置，否则使用表格的优先设置
 * @param node {qc.Node} - 需要计算的节点
 * @param style {{}} - 节点的样式
 */
TableLayout.prototype.getCellRect = function(node, force) {
    return qc.Bounds.getBox(node, this.contentSizeProvider, force, 0, this.gameObject);
};

/**
 * 得到一个单元格的样式，返回的为拷贝，无法直接进行设置，如果需要设置，请使用setCellStyle接口
 * 当获取样式时，对于每个细节参数（水平对齐方式，垂直对齐方式，上、右、下、左边距）优先返回本单元格设置的样式，如果没有单独设置，则返回行或者列中较晚设置的样式，
 * 如果行列也没有，则返回全局设置
 * @param column {Number | null} - 要返回的单元格的列，如果为 null，则表示返回指定行的样式，如果为 null 且row === null,则返回全局样式
 * @param row {Number | null} - 要返回的单元格的行，如果为 null，则表示返回指定列的样式，如果为 null 且column === null,则返回全局样式
 * @returns {{align: （Number}, timestamp: (number), paddingLeft: (number), paddingRight: (number), paddingTop: (number), paddingBottom: (number)}}
 */
TableLayout.prototype.getCellStyle = function(column, row) {
    // 每行，每列的第0个用来记录通用属性，所以要将列和行的数值+1
    column = (column == null) ? 0 : column + 1;
    row = (row == null) ? 0 : row + 1;
    var baseStyle = this._cellStyle[0][0];
    // 确定行样式
    var rowStyle = this._cellStyle[row] && this._cellStyle[row][0];
    // 确认列样式
    var colStyle = this._cellStyle[0][column];

    if (!colStyle || (rowStyle && rowStyle.timestamp > colStyle.timestamp)) {
        colStyle && (baseStyle = this._mixinStyle(baseStyle, colStyle));
        rowStyle && (baseStyle = this._mixinStyle(baseStyle, rowStyle));
    }
    else {
        rowStyle && (baseStyle = this._mixinStyle(baseStyle, rowStyle));
        colStyle && (baseStyle = this._mixinStyle(baseStyle, colStyle));
    }

    var cellStyle = this._cellStyle[row] && this._cellStyle[row][column];
    cellStyle && (baseStyle = this._mixinStyle(baseStyle, cellStyle));
    baseStyle.paddingTop = isNaN(baseStyle.paddingTop) ? 0 : baseStyle.paddingTop;
    baseStyle.paddingLeft = isNaN(baseStyle.paddingLeft) ? 0 : baseStyle.paddingLeft;
    baseStyle.paddingRight = isNaN(baseStyle.paddingRight) ? 0 : baseStyle.paddingRight;
    baseStyle.paddingBottom = isNaN(baseStyle.paddingBottom) ? 0 : baseStyle.paddingBottom;
    baseStyle.cellWidth = isNaN(baseStyle.cellWidth) ? 0 : baseStyle.cellWidth;
    baseStyle.cellHeight = isNaN(baseStyle.cellHeight) ? 0 : baseStyle.cellHeight;
    return baseStyle;
};

/**
 * 设置一个单元格样式
 * @param column {Number | null} - 要设置的单元格的列，如果为 null，则表示设定指定行的样式，如果为 null 且row === null,则设置全局样式
 * @param row {Number | null} - 要设置的单元格的行，如果为 null，则表示设定指定列的样式，如果为 null 且column === null,则设置全局样式
 * @param align {Number} - 要设置的对齐格式
 * @param paddingTop {Number} - 单元格内边框的上边距
 * @param paddingRight {Number} - 单元格内边框的右边距
 * @param paddingBottom {Number} - 单元格内边框的下边距
 * @param paddingLeft {Number} - 单元格内边框的左边距
 * @param cellWidth {Number} - 单元格的最小宽度
 * @param cellHeight {Number} - 单元格的最小高度
 */
TableLayout.prototype.setCellStyle = function(column, row, align, paddingTop, paddingRight, paddingBottom, paddingLeft, cellWidth, cellHeight) {
    column = (column == null) ? 0 : column + 1;
    row = (row == null) ? 0 : row + 1;
    if (!this._cellStyle[row]) {
        this._cellStyle[row] = [];
    }
    if (!this._cellStyle[row][column])
        this._cellStyle[row][column] = this._buildCellInfo();
    var cell = this._cellStyle[row][column];
    align != null && (cell.align = align);
    paddingTop != null && (cell.paddingTop = paddingTop);
    paddingRight != null && (cell.paddingRight = paddingRight);
    paddingBottom != null && (cell.paddingBottom = paddingBottom);
    paddingLeft != null && (cell.paddingLeft = paddingLeft);
    cellWidth != null && (cell.cellWidth = cellWidth);
    cellHeight != null & (cell.cellHeight = cellHeight);
    this._cellStyle[row][column].timestamp = this.gameObject.game.time.fixedTime;
    this.rebuildTable();
};

/**
 * 清除一个单元格样式
 * @param column {Number | null} - 要清除的单元格的列，如果为 null，则表示清除指定行的样式，如果为 null 且row === null,则清除全局样式
 * @param row {Number | null} - 要清除的单元格的行，如果为 null，则表示清除指定列的样式，如果为 null 且column === null,则清除全局样式
 */
TableLayout.prototype.clearCellStyle = function(column, row) {
    column = (column == null) ? 0 : column + 1;
    row = (row == null) ? 0 : row + 1;
    if (column === 0 && row === 0) {
        this._cellStyle[0][0] = this._buildCellInfo();
        return;
    }
    this._cellStyle[row] && this._cellStyle[row][column] &&
    (this._cellStyle[row][column] = null);
};

/**
 * 创建一个默认的单元格样式
 */
TableLayout.prototype._buildCellInfo = function() {
    return {
        align : 0,
        timestamp : 0,
        paddingLeft : NaN,
        paddingRight : NaN,
        paddingTop : NaN,
        paddingBottom : NaN,
        cellWidth : NaN,
        cellHeight : NaN
    };
};

/**
 * 计算当前的行列信息
 * @private
 */
TableLayout.prototype._calcColumnRow = function() {
    var totalChildren = this._getChildrenCount();
    var stride = this.stride;
    var constraint = this.constraint;
    if (constraint === TableLayout.CONSTRAINT_FLEXIBLE) {
        var style = this.getCellStyle(null, null);
        var rect = qc.Bounds.getContentRect(this.gameObject);
        if (this.startAxis === TableLayout.AXIS_HORIZONTAL) {
            stride = Math.min(Math.max(1, Math.floor((rect.width + this.spacingX) / (style.cellWidth + this.spacingX))), totalChildren);
            constraint = TableLayout.CONSTRAINT_FIX_COLUMN_COUNT;
        }
        else {
            stride = Math.min(Math.max(1, Math.floor((rect.height + this.spacingY) / (style.cellHeight + this.spacingY))), totalChildren);
            constraint = TableLayout.CONSTRAINT_FIX_ROW_COUNT;
        }
    }
    // 计算最后排布的行数和列数，
    var columnCount = constraint === TableLayout.CONSTRAINT_FIX_ROW_COUNT ? Math.ceil(totalChildren / stride) : stride;
    var rowCount = constraint === TableLayout.CONSTRAINT_FIX_ROW_COUNT ? stride : Math.ceil(totalChildren / stride);

    return new qc.Point(columnCount, rowCount);
};

/**
 * 计算子节点在表格中的位置，当不限制行数和列数时无效
 * @param index
 * @param pos
 * @returns {qc.Point}
 * @private
 */
TableLayout.prototype._calcPos = function(index, pos) {
    var size = this._calcColumnRow();

    pos = pos || new qc.Point(0,0);
    if (this.startAxis === TableLayout.AXIS_HORIZONTAL) {
        pos.x = index % size.x;
        pos.y = Math.floor(index / size.x);
    }
    else {
        pos.x = Math.floor(index / size.y);
        pos.y = index % size.y;
    }
    (this.startCorner & 0xF0) !== 0 && (pos.x = size.x - 1 - pos.x);
    (this.startCorner & 0x0F) !== 0 && (pos.y = size.y - 1 - pos.y);
    return pos;
};

/**
 * 调整单元格的位置
 * @param cell {{}} - 单元格信息
 * @param targetBounds {qc.Rectangle} - 目标区域
 * @private
 */
TableLayout.prototype._setCellPosition = function(cell, targetBounds) {
    if (! this.ignoreX || this.columnsCount > 1) {
        var x = cell.node.x;
        if ((cell.style.align & TableLayout.ALIGN_RIGHT) !== 0) {
            x += targetBounds.x - cell.bounds.x + (targetBounds.width - cell.bounds.width);
        }
        else if ((cell.style.align & TableLayout.ALIGN_CENTER) !== 0) {
            x += targetBounds.x - cell.bounds.x + (targetBounds.width - cell.bounds.width) / 2;
        }
        else {
            x += targetBounds.x - cell.bounds.x;
        }
        if (Math.abs(x - cell.node.x) > TableLayout._precision) {
            cell.node.x = x;
        }
    }
    if (!this.ignoreY || this.rowsCount > 1) {
        var y = cell.node.y;
        if ((cell.style.align & TableLayout.ALIGN_BOTTOM) !== 0) {
            y += targetBounds.y - cell.bounds.y + (targetBounds.height - cell.bounds.height);
        }
        else if ((cell.style.align & TableLayout.ALIGN_MIDDLE) !== 0) {
            y += targetBounds.y - cell.bounds.y + (targetBounds.height - cell.bounds.height) / 2;
        }
        else {
            y += targetBounds.y - cell.bounds.y;
        }
        if (Math.abs(y - cell.node.y) > TableLayout._precision) {
            cell.node.y = y;
        }
    }
};

/**
 * 设置节点的显示区域
 * @param cell
 * @param targetBounds
 * @param width
 * @param height
 * @private
 */
TableLayout.prototype._setCellSize = function(cell, width, height) {
    // 首先调整节点大小到指定大小
    if (Math.abs(cell.bounds.width - width) < TableLayout._precision &&
        Math.abs(cell.bounds.height - height) < TableLayout._precision) {
        return;
    }
    var margin = Bounds.getMargin(cell.node);
    var nodeExpectWidth = width - margin.left - margin.right,
        nodeExpectHeight = height - margin.top - margin.bottom,
        boundsWith = cell.bounds.width  - margin.left - margin.right,
        boundsHeight = cell.bounds.height  - margin.top - margin.bottom;
    cell.node.setAnchor(new qc.Point(0, 0), new qc.Point(0, 0), false);
    cell.node.x = 0;
    cell.node.y = 0;
    boundsWith <= 0 ? (cell.node.width = nodeExpectWidth) :
        (cell.node.width *= nodeExpectWidth / boundsWith);
    boundsHeight <=0 ? (cell.node.height = nodeExpectHeight) :
        (cell.node.height *= nodeExpectHeight / boundsHeight);
    cell.node.setStretch(0, 0, 0, 0);
    cell.bounds = this.getCellRect(cell.node, true);
};

/**
 * 重新布局
 */
TableLayout.prototype.relayout = function() {
    this.rebuildTable();
};

/**
 * 调整表格
 */
TableLayout.prototype.rebuildTable = function() {
    var style = this.getCellStyle(null, null);
    if (this.style === TableLayout.STYLE_WRAP_ELEMENT) {
        this._rebuildWrapTable();
    }
    else {
        this._rebuildResizeTable();
    }
};

/**
 * 获取在某一轴线上的默认布局系数
 * @private
 */
TableLayout.prototype._getDefaultLayout = function(axis){
    return {min : 0,
        preferred : 0,
        flexible : 0,
        extra : 0};
};

/**
 * 获取节点的布局系数
 * @param node
 * @private
 */
TableLayout.prototype.getCellLayout = function(node) {
    return qc.LayoutElement.getLayoutElement(node);
};

/**
 * 调整动态模式
 * @private
 */
TableLayout.prototype._rebuildResizeTable = function() {
    var children = this._getChildren();
    var cellSize = [];
    var columnsWidth = [];
    var rowsHeight = [];
    var widthArgs = [];
    var heightArgs = [];
    var len = children.length;
    var pos = new qc.Point(0, 0);

    // 收集信息，取每行每列的信息进行整合
    var idx = -1;
    while (++idx < len) {
        this._calcPos(idx, pos);
        var layout = this.getCellLayout(children[idx]);
        var style = this.getCellStyle(pos.x, pos.y);
        var box = this.getCellRect(children[idx]);
        (cellSize[pos.y] || (cellSize[pos.y] = []))[pos.x] = {
            node : children[idx],
            layout : layout,
            bounds : box,
            style : style
        };
        var widthInfo = widthArgs[pos.x] || (widthArgs[pos.x] = this._getDefaultLayout('x'));
        var heightInfo = heightArgs[pos.y] || (heightArgs[pos.y] = this._getDefaultLayout('y'));
        // 调整首选尺寸
        layout.preferredWidth < 0 && children[idx].nativeSize && (layout.preferredWidth = children[idx].nativeSize.width);
        layout.preferredHeight < 0 && children[idx].nativeSize && (layout.preferredHeight = children[idx].nativeSize.height);

        if (!isNaN(style.cellWidth) && style.cellWidth > 0) {
            layout.minWidth = style.cellWidth;
            layout.preferredWidth = style.cellWidth;
        }
        if (!isNaN(style.cellHeight) && style.cellHeight > 0) {
            layout.minHeight = style.cellHeight;
            layout.preferredHeight = style.cellHeight;
        }

        layout.minWidth >= 0 && (widthInfo.min = Math.max(widthInfo.min, layout.minWidth));
        layout.minHeight >= 0 && (heightInfo.min = Math.max(heightInfo.min, layout.minHeight));

        widthInfo.preferred = Math.max(widthInfo.preferred, layout.preferredWidth, widthInfo.min);
        heightInfo.preferred = Math.max(heightInfo.preferred, layout.preferredHeight, widthInfo.min);

        layout.flexibleWidth >= 0 && (widthInfo.flexible = Math.max(widthInfo.flexible, layout.flexibleWidth));
        layout.flexibleHeight >= 0 && (heightInfo.flexible = Math.max(heightInfo.flexible, layout.flexibleHeight));

        widthInfo.extra = Math.max(widthInfo.extra, style.paddingLeft + style.paddingRight);
        heightInfo.extra = Math.max(heightInfo.extra, style.paddingTop + style.paddingBottom);
    }

    // 计算全局信息
    var totalWidthInfo = {
        min : 0,
        preferred : 0,
        flexible : 0,
        extra : 0,
        fixed : 0
    };
    var totalHeightInfo = {
        min : 0,
        preferred : 0,
        flexible : 0,
        extra : 0,
        fixed : 0
    };
    widthArgs.forEach(function(v){
        totalWidthInfo.min += v.min;
        totalWidthInfo.preferred += v.preferred;
        totalWidthInfo.flexible += v.flexible;
        totalWidthInfo.extra += v.extra;
    });
    heightArgs.forEach(function(v){
        totalHeightInfo.min += v.min;
        totalHeightInfo.preferred += v.preferred;
        totalHeightInfo.flexible += v.flexible;
        totalHeightInfo.extra += v.extra;
    });
    // 开始计算各个格子的大小
    var rect = qc.Bounds.getContentRect(this.gameObject);
    var totalSpacingX = Math.max(0, widthArgs.length - 1) * this.spacingX;
    var totalSpacingY = Math.max(0, heightArgs.length - 1) * this.spacingY;
    var offset = new qc.Point(0, 0);

    // 水平分布
    if (rect.width <= totalWidthInfo.min + totalWidthInfo.extra + totalSpacingX) {
        // 可用区域小于需要的最小值，按最小值分配
        for (var i = 0; i < widthArgs.length; ++i) {
            if (!widthArgs[i]) {
                columnsWidth[i] = 0;
                continue;
            }
            columnsWidth[i] = widthArgs[i].min + widthArgs[i].extra;
        }
    }
    else if (rect.width <= totalWidthInfo.preferred + totalWidthInfo.extra + totalSpacingX) {
        // 可用区域小于首选需要的值时，将大于min需求的部分按首选值的权重分配
        var divisible = rect.width - totalWidthInfo.extra - totalWidthInfo.min - totalSpacingX;
        for (var i = 0; i < widthArgs.length; ++i) {
            if (!widthArgs[i]) {
                columnsWidth[i] = 0;
                continue;
            }
            columnsWidth[i] = divisible * widthArgs[i].preferred / (totalWidthInfo.preferred || 1) +
            widthArgs[i].min + widthArgs[i].extra;
        }
    }
    else {

        // 如果还有多余的空间，按可变区域权重分配
        var divisible = rect.width - totalWidthInfo.extra - totalWidthInfo.preferred - totalSpacingX;
        for (var i = 0; i < widthArgs.length; ++i) {
            if (!widthArgs[i]) {
                columnsWidth[i] = 0;
                continue;
            }
            columnsWidth[i] = divisible * widthArgs[i].flexible / (totalWidthInfo.flexible || 1) +
            widthArgs[i].preferred + widthArgs[i].extra;
        }
        // 都不可拓展，则需要按对齐方式调整偏移
        if (totalWidthInfo.flexible === 0) {
            if ((this.contentAlignment & TableLayout.ALIGN_RIGHT) !== 0) {
                offset.x = divisible;
            }
            else if ((this.contentAlignment & TableLayout.ALIGN_CENTER) !== 0) {
                offset.x = divisible / 2;
            }
        }
    }

    // 垂直分布
    if (rect.height <= totalHeightInfo.min + totalHeightInfo.extra + totalSpacingY) {
        // 可用区域小于需要的最小值，按最小值分配
        for (var i = 0; i < heightArgs.length; ++i) {
            if (!heightArgs[i]) {
                rowsHeight[i] = 0;
                continue;
            }
            rowsHeight[i] = heightArgs[i].min + heightArgs[i].extra;
        }
    }
    else if (rect.height <= totalHeightInfo.preferred + totalHeightInfo.extra + totalSpacingY) {
        // 可用区域小于首选需要的值时，将大于min需求的部分按首选值的权重分配
        var divisible = rect.height - totalHeightInfo.extra - totalHeightInfo.min - totalSpacingY;
        for (var i = 0; i < heightArgs.length; ++i) {
            if (!heightArgs[i]) {
                rowsHeight[i] = 0;
                continue;
            }
            rowsHeight[i] = divisible * heightArgs[i].preferred / (totalHeightInfo.preferred || 1) +
            heightArgs[i].min + heightArgs[i].extra;
        }
    }
    else {
        // 如果还有多余的空间，按可变区域权重分配
        var divisible = rect.height - totalHeightInfo.extra - totalHeightInfo.preferred - totalSpacingY;
        for (var i = 0; i < heightArgs.length; ++i) {
            if (!heightArgs[i]) {
                rowsHeight[i] = 0;
                continue;
            }
            rowsHeight[i] = divisible * heightArgs[i].flexible / (totalHeightInfo.flexible || 1) +
            heightArgs[i].preferred + heightArgs[i].extra;
        }
        // 都不可拓展，则需要按对齐方式调整偏移
        if (totalHeightInfo.flexible === 0) {
            if ((this.contentAlignment & TableLayout.ALIGN_BOTTOM) !== 0) {
                offset.y = divisible;
            }
            else if ((this.contentAlignment & TableLayout.ALIGN_MIDDLE) !== 0) {
                offset.y = divisible / 2;
            }
        }
    }

    // 进行显示
    var selfBounds = qc.Bounds.getContentRect(this.gameObject);
    var targetBounds = new qc.Rectangle(0, 0, 0, 0);
    var startWidth = 0;
    var startHeight = 0;
    for (var y = 0; y < rowsHeight.length; y++) {
        startWidth = 0;
        for (var x = 0; x < columnsWidth.length; x++ ) {
            var cell = cellSize[y][x];
            if (!cell) {
                startWidth += columnsWidth[x] + this.spacingX;
                continue;
            }
            targetBounds.x = selfBounds.x + startWidth + cell.style.paddingLeft  + offset.x;
            targetBounds.y = selfBounds.y + startHeight + cell.style.paddingTop + offset.y;
            targetBounds.width = columnsWidth[x] - cell.style.paddingLeft - cell.style.paddingRight;
            targetBounds.height = rowsHeight[y] - cell.style.paddingTop - cell.style.paddingBottom;
            var cellWidth = targetBounds.width;
            var cellHeight = targetBounds.height;
            if (cell.layout.flexibleWidth <= 0 && targetBounds.width > cell.layout.preferredWidth && cell.layout.preferredWidth > 0) {
                cellWidth = cell.layout.preferredWidth;
            }
            if (cell.layout.flexibleHeight <= 0 && targetBounds.height > cell.layout.preferredHeight && cell.layout.preferredHeight > 0) {
                cellHeight = cell.layout.preferredHeight;
            }
            this._setCellSize(cell, cellWidth, cellHeight);
            this._setCellPosition(cell, targetBounds);

            startWidth += columnsWidth[x] + this.spacingX;
        }
        startHeight += rowsHeight[y] + this.spacingY;
    }
};

/**
 * 调整固定列或者行的表格
 */
TableLayout.prototype._rebuildWrapTable = function() {
    var children = this._getChildren();
    var cellSize = [];
    var columnsWidth = [];
    var rowsHeight = [];
    var len = children.length;
    var pos = new qc.Point(0, 0);
    while (len--) {
        this._calcPos(len, pos);
        var style = this.getCellStyle(pos.x, pos.y);
        var box = this.getCellRect(children[len]);
        (cellSize[pos.y] || (cellSize[pos.y] = []))[pos.x] = {
            node : children[len],
            bounds : box,
            style : style
        };
        // 自由模式下，以设定的宽高为宽高，否则只是作为最小宽高进行处理
        if (style.cellWidth > 0) {
            columnsWidth[pos.x] = style.cellWidth;
        }
        else {
            columnsWidth[pos.x] = Math.max(box.width + style.paddingLeft + style.paddingRight, columnsWidth[pos.x] || 0, style.cellWidth);
        }
        if (style.cellHeight > 0)
        {
            rowsHeight[pos.y] = style.cellHeight;
        }
        else {
            rowsHeight[pos.y] = Math.max(box.height + style.paddingTop + style.paddingBottom, rowsHeight[pos.y]|| 0, style.cellHeight);
        }
    }

    var totalWidth = 0;
    var totalHeight = 0;
    for (var x = 0; x < columnsWidth.length; x++ ) {
        totalWidth += columnsWidth[x];
    }
    for (var y = 0; y < rowsHeight.length; y++) {
        totalHeight += rowsHeight[y];
    }

    var selfBounds = qc.Bounds.getContentRect(this.gameObject);
    var offset = new qc.Point(0, 0);
    if ((this.contentAlignment & TableLayout.ALIGN_BOTTOM) !== 0) {
        offset.y = selfBounds.height - totalHeight;
    }
    else if ((this.contentAlignment & TableLayout.ALIGN_MIDDLE) !== 0) {
        offset.y = (selfBounds.height - totalHeight) / 2;
    }
    if ((this.contentAlignment & TableLayout.ALIGN_RIGHT) !== 0) {
        offset.x = selfBounds.width - totalWidth;
    }
    else if ((this.contentAlignment & TableLayout.ALIGN_CENTER) !== 0) {
        offset.x = (selfBounds.width - totalWidth) / 2;
    }


    var targetBounds = new qc.Rectangle(0, 0, 0, 0);
    var startWidth = 0;
    var startHeight = 0;
    for (var y = 0; y < rowsHeight.length; y++) {
        startWidth = 0;
        for (var x = 0; x < columnsWidth.length; x++ ) {
            var cell = cellSize[y][x];
            if (!cell) {
                var style = this.getCellStyle(x, y);
                var cellWidth = columnsWidth[x] === undefined ? style.cellWidth : columnsWidth[x];
                startWidth += cellWidth + this.spacingX;
                continue;
            }

            targetBounds.x = selfBounds.x + startWidth + cell.style.paddingLeft + offset.x;
            targetBounds.y = selfBounds.y + startHeight + cell.style.paddingTop + offset.y;
            targetBounds.width = columnsWidth[x] - cell.style.paddingLeft - cell.style.paddingRight;
            targetBounds.height = rowsHeight[y] - cell.style.paddingTop - cell.style.paddingBottom;

            this._setCellPosition(cell, targetBounds);

            startWidth += columnsWidth[x] + this.spacingX;
        }
        startHeight += rowsHeight[y] + this.spacingY;
    }
};

/**
 * 当子节点变化时
 * @private
 */
TableLayout.prototype._doChildrenChanged = function(event) {
    var len = event.children.length;
    if (event.type === 'add') {
        while (len--) {
            if (event.children[len]) {
                event.children[len].onLayoutArgumentChanged.add(this._doLayoutArgumentChanged, this);
            }
        }
    }
    else if (event.type === 'remove') {
        while (len--) {
            if (event.children[len]) {
                event.children[len].onLayoutArgumentChanged.remove(this._doLayoutArgumentChanged, this);
            }
        }
    }
    this._needRebuildTable = true;
};

TableLayout.prototype._doLayoutArgumentChanged = function() {
    this._needRebuildTable = true;
};


/**
 * 不限制行数和列数，根据大小自动进行调整
 * @constant
 * @type {number}
 */
TableLayout.CONSTRAINT_FLEXIBLE = 0;
/**
 * 限制列数
 * @constant
 * @type {number}
 */
TableLayout.CONSTRAINT_FIX_COLUMN_COUNT = 1;
/**
 * 限制行数
 * @constant
 * @type {number}
 */
TableLayout.CONSTRAINT_FIX_ROW_COUNT = 2;

/**
 * 保持子节点尺寸的情况下进行布局，Bounds会随着子节点的变化而变化
 * @constant
 * @type {number}
 */
TableLayout.STYLE_WRAP_ELEMENT = 0;

/**
 * 保持本节点的RectTransform，改变子节点的尺寸进行布局
 * @constant
 * @type {number}
 */
TableLayout.STYLE_RESIZE_ELEMENT = 1;

/**
 * 使用 RectTransform 作为布局尺寸提供者
 * RectTransform不受 Scale，Rotation 参数影响
 * @constant
 * @type {number}
 */
TableLayout.USE_RECTTRANSFORM = qc.LayoutElement.USE_RECTTRANSFORM;

/**
 * 使用Bounds作为布局尺寸提供者
 * Bounds为实现显示节点需要的尺寸
 * @constant
 * @type {number}
 */
TableLayout.USE_BOUNDS = qc.LayoutElement.USE_BOUNDS;

/**
 * 水平方向
 * @constant
 * @type {number}
 */
TableLayout.AXIS_HORIZONTAL = 0;
/**
 * 垂直方向
 * @constant
 * @type {number}
 */
TableLayout.AXIS_VERTICAL = 1;

/**
 * 左上角
 * @constant
 * @type {number}
 */
TableLayout.CORNER_TOP_LEFT = 0x00;
/**
 * 右上角
 * @constant
 * @type {number}
 */
TableLayout.CORNER_TOP_RIGHT = 0x10;
/**
 * 右下角
 * @constant
 * @type {number}
 */
TableLayout.CORNER_BOTTOM_RIGHT = 0x11;

/**
 * 左下角
 * @constant
 * @type {number}
 */
TableLayout.CORNER_BOTTOM_LEFT = 0x01;

/**
 * 水平居左
 * @constant
 * @type {number}
 */
TableLayout.ALIGN_LEFT = 0x01;
/**
 * 水平居中
 * @constant
 * @type {number}
 */
TableLayout.ALIGN_CENTER = 0x02;
/**
 * 水平居右
 * @constant
 * @type {number}
 */
TableLayout.ALIGN_RIGHT = 0x04;
/**
 * 垂直居上
 * @constant
 * @type {number}
 */
TableLayout.ALIGN_TOP = 0x10;
/**
 * 垂直居中
 * @constant
 * @type {number}
 */
TableLayout.ALIGN_MIDDLE = 0x20;
/**
 * 垂直居下
 * @constant
 * @type {number}
 */
TableLayout.ALIGN_BOTTOM = 0x40;

/**
 * 左上
 * @constant
 * @type {number}
 */
TableLayout.ALIGN_TOP_LEFT = TableLayout.ALIGN_TOP | TableLayout.ALIGN_LEFT;
/**
 * 中上
 * @constant
 * @type {number}
 */
TableLayout.ALIGN_TOP_CENTER = TableLayout.ALIGN_TOP | TableLayout.ALIGN_CENTER;
/**
 * 右上
 * @constant
 * @type {number}
 */
TableLayout.ALIGN_TOP_RIGHT = TableLayout.ALIGN_TOP | TableLayout.ALIGN_RIGHT;
/**
 * 左中
 * @constant
 * @type {number}
 */
TableLayout.ALIGN_MIDDLE_LEFT = TableLayout.ALIGN_MIDDLE | TableLayout.ALIGN_LEFT;
/**
 * 中心
 * @constant
 * @type {number}
 */
TableLayout.ALIGN_MIDDLE_CENTER = TableLayout.ALIGN_MIDDLE | TableLayout.ALIGN_CENTER;
/**
 * 右中
 * @constant
 * @type {number}
 */
TableLayout.ALIGN_MIDDLE_RIGHT = TableLayout.ALIGN_MIDDLE | TableLayout.ALIGN_RIGHT;
/**
 * 左下
 * @constant
 * @type {number}
 */
TableLayout.ALIGN_BOTTOM_LEFT = TableLayout.ALIGN_BOTTOM | TableLayout.ALIGN_LEFT;
/**
 * 中下
 * @constant
 * @type {number}
 */
TableLayout.ALIGN_BOTTOM_CENTER = TableLayout.ALIGN_BOTTOM | TableLayout.ALIGN_CENTER;
/**
 * 右下
 * @constant
 * @type {number}
 */
TableLayout.ALIGN_BOTTOM_RIGHT = TableLayout.ALIGN_BOTTOM | TableLayout.ALIGN_RIGHT;
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 控制目标节点的长、宽比例组件
 * @class qc.AspectRatioFitter
 */
var AspectRatioFitter = defineBehaviour('qc.AspectRatioFitter', qc.Behaviour, function() {
    this.mode = AspectRatioFitter.NONE;
    this.ratio = 1;

    // 在编辑器模式下需要运行
    this.runInEditor = true;
}, {
    mode : qc.Serializer.NUMBER,
    ratio : qc.Serializer.NUMBER
});

// 菜单上的显示
AspectRatioFitter.__menu = 'UI/AspectRatioFitter';

Object.defineProperties(AspectRatioFitter.prototype,{
    /**
     * @property {number} mode - 控制比例的方式
     */
    mode : {
        get : function() { return this._mode || AspectRatioFitter.NONE; },
        set : function(v) {
            if (this.mode === v) return;
            this._mode = v;
            this._reset();
        }
    },

    /**
     * @property {number} ratio - 长宽的比例
     */
    ratio : {
        get : function() { return this._ratio || 1; },
        set : function(v) {
            if (v <= 0) v = 0.001;
            if (this.ratio === v) return;
            this._ratio = v;
            this._reset();
        }
    }
});

/**
 * 几种控制模式：无、长决定高、高决定长、适合父亲大小、填满父亲大小
 * @type {number}
 */
AspectRatioFitter.NONE = 0;
AspectRatioFitter.WIDTH_CONTROLS_HEIGHT = 1;
AspectRatioFitter.HEIGHT_CONTROLS_WIDTH = 2;
AspectRatioFitter.FIT_IN_PARENT = 3;
AspectRatioFitter.ENVELOPE_PARENT = 4;

/**
 * 启动时立刻调整下大小
 */
AspectRatioFitter.prototype.awake = function() {
    var self = this;
    self._reset();

    // relayout时需要重新调整一次大小
    var node = self.gameObject;
    self.addListener(node.onRelayout, function() {
        self._reset();
    });
}

/**
 * 重新根据模式调整下长和宽
 * @private
 */
AspectRatioFitter.prototype._reset = function() {
    if (this.mode === AspectRatioFitter.NONE) return;
    var node = this.gameObject;

    // 宽度决定高度
    if (this.mode === AspectRatioFitter.WIDTH_CONTROLS_HEIGHT) {
        node.height = node.width / this.ratio;
        return;
    }

    // 高度决定宽度
    if (this.mode === AspectRatioFitter.HEIGHT_CONTROLS_WIDTH) {
        node.width = node.height * this.ratio;
        return;
    }

    // 根据父亲来决定大小
    var rect = node.parent.rect;
    node.setAnchor(new qc.Point(0, 0), new qc.Point(1, 1));
    if (this.mode === AspectRatioFitter.FIT_IN_PARENT) {
        // 刚好在父亲的范围里面不超出去
        var w = rect.width;
        var h = w / this.ratio;
        if (h > rect.height) {
            h = rect.height;
            w = h * this.ratio;
        }

        // 设置边距
        node.setStretch((rect.width - w) / 2, (rect.width - w) / 2,
            (rect.height - h) / 2, (rect.height - h) / 2);
        return;
    }

    if (this.mode === AspectRatioFitter.ENVELOPE_PARENT) {
        // 填满父亲的矩形框
        var w = rect.width;
        var h = w / this.ratio;
        if (h < rect.height) {
            h = rect.height;
            w = h * this.ratio;
        }

        // 设置边距
        node.setStretch((rect.width - w) / 2, (rect.width - w) / 2,
            (rect.height - h) / 2, (rect.height - h) / 2);
        return;
    }
}

/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 为节点提供缓存为图片功能，提升绘制效率
 * @class qc.CacheAsBitmap
 */
var CacheAsBitmap = defineBehaviour('qc.CacheAsBitmap', qc.Behaviour, function() {
    // 设置默认尺寸提供者
    this._boundsType = CacheAsBitmap.BOUNDS_SELF;

    // 设置默认的缓存方式
    this._cacheType = CacheAsBitmap.CACHE_FOR_SCREEN;

    // 缓存是否需要更新
    this._dirty = true;

    // 是否限制缓存区域大小 
    this.boundsInScreen = false;

    // 编辑器模式下需要执行
    this.runInEditor = true;

    // 用来记录Bounds和rect的偏移
    this.offX = 0;
    this.offY = 0;
},{
    boundsType : qc.Serializer.NUMBER,
    cacheType : qc.Serializer.NUMBER,
    boundsInScreen : qc.Serializer.BOOLEAN
});

// 菜单上的显示
CacheAsBitmap.__menu = 'UI/CacheAsBitmap';

Object.defineProperties(CacheAsBitmap.prototype,{
    /**
     * @property {number} boundsType - 缓存的边界类型
     */
    boundsType: {
        get : function() { return this._boundsType; },
        set : function(value) {
            if (this._boundsType === value) {
                return;
            }
            this._boundsType = value;
            this.dirty = true;
        }
    },

    /**
     * @property {number} cacheType - 缓存的方式
     */
    cacheType: {
        get : function() { return this._cacheType; },
        set : function(value) {
            if (this._cacheType === value) {
                return;
            }
            this._cacheType = value;
            this.dirty = true;
        }
    },

    /**
     * @property {boolean} dirty - 缓存是否为脏，如果为脏，再绘制前会刷新缓存
     */
    dirty: {
        get: function() { return this._dirty; },
        set: function(value) {
            if (this._dirty === value) {
                return;
            }
            this._dirty = value;
        }
    }
});

/**
 * 初始化处理
 */
CacheAsBitmap.prototype.awake = function() {
    var self = this,
        o = self.gameObject;
    self.game.assets.webFontLoaded.addOnce(self._setDirty, self);
    o.onRelayout.add(self._setDirty, self);
};

CacheAsBitmap.prototype._setDirty = function() {
    this.dirty = true;
};

/**
 * 启用时
 */
CacheAsBitmap.prototype.onEnable = function() {
    var self = this,
        gameObject = self.gameObject;
    if (gameObject._destroy) {
        return;
    }

    // 缓存不存在需要创建
    if (!self._cache) {
        self._cache = new qc.RenderTexture(100, 100, this.game.phaser.render, null, this.game.resolution);
        self._cacheSprite = new PIXI.Sprite(self._cache);
    }

    self.dirty = true;

    // 替换绘制函数
    if (!self._nodeRenderCanvas) {
        self._nodeRenderCanvas = self.gameObject.phaser._renderCanvas;
        self.gameObject.phaser._renderCanvas = self.renderCanvas.bind(self);
    }

    if (!self._nodeRenderWebGL) {
        self._nodeRenderWebGL = self.gameObject.phaser._renderWebGL;
        self.gameObject.phaser._renderWebGL = self.renderWebGL.bind(self);
    }
};

/**
 * 禁用时
 */
CacheAsBitmap.prototype.onDisable = function() {
    var self = this;
    if (self._nodeRenderCanvas) {
        self.gameObject.phaser._renderCanvas = self._nodeRenderCanvas;
        self._nodeRenderCanvas = null;
    }
    if (self._nodeRenderWebGL) {
        self.gameObject.phaser._renderWebGL = self._nodeRenderWebGL;
        self._nodeRenderWebGL = null;
    }
};

/**
 * 销毁时
 */
CacheAsBitmap.prototype.onDestroy = function() {
    var self = this;
    if (self._nodeRenderCanvas) {
        self.gameObject.phaser._renderCanvas = self._nodeRenderCanvas;
        self._nodeRenderCanvas = null;
    }
    if (self._nodeRenderWebGL) {
        self.gameObject.phaser._renderWebGL = self._nodeRenderWebGL;
        self._nodeRenderWebGL = null;
    }
    if (self._cache) {
        self._cache.destroy(true);
        self._cache = null;
    }
    if (self._cacheSprite) {
        self._cacheSprite = null;
    }

    // 干掉事件监听
    self.gameObject.onRelayout.remove(self._setDirty, self);
};

/**
 * 更新缓存
 * @private
 */
CacheAsBitmap.prototype._updateCache = function() {
    var self = this,
        cache = self._cache,
        sprite = self._cacheSprite;
    self._updatingCache = true;
    var bounds = null;
    if (this._cacheType !== CacheAsBitmap.CACHE_FOR_SCREEN)
        bounds = self._cacheBounds = self.getLocalBounds();
    else 
        bounds = self._cacheBounds = self.getBounds();

    if (self.boundsInScreen) {
        // 限制范围为屏幕范围
        if (bounds.x < 0) {
            bounds.width += bounds.x;
            bounds.x = 0;
        }

        if (bounds.y < 0) {
            bounds.height += bounds.y;
            bounds.y = 0;
        }

        // 将宽高整数化
        bounds.width = Math.round(Math.max(1, Math.min(self.game.width * self.game.resolution - bounds.x, bounds.width)));
        bounds.height = Math.round(Math.max(1, Math.min(self.game.height * self.game.resolution - bounds.y, bounds.height)));    
    }
    else {
        bounds.width = Math.round(bounds.width);
        bounds.height = Math.round(bounds.height);
    }
    
    // 这里将缓存的宽高修改为2的倍数，在部分机型的uc浏览器中，如果创建的canvas为奇数，会导致无法显示
    cache.resize(bounds.width + (bounds.width & 1), bounds.height + (bounds.height & 1), true);
    var wt = self.gameObject.worldTransform;
    if (this._cacheType !== CacheAsBitmap.CACHE_FOR_SCREEN) {
        var swt = sprite.worldTransform;
        swt.a = wt.a;
        swt.b = wt.b;
        swt.c = wt.c;
        swt.d = wt.d;
        swt.tx = wt.tx;
        swt.ty = wt.ty;

        PIXI.DisplayObject._tempMatrix.tx = -bounds.x;
        PIXI.DisplayObject._tempMatrix.ty = -bounds.y;

        cache.render(self.gameObject.phaser, PIXI.DisplayObject._tempMatrix, true);

        sprite.anchor.x = -( bounds.x / bounds.width );
        sprite.anchor.y = -( bounds.y / bounds.height );
    }
    else {
        self.offX = bounds.x - wt.tx;
        self.offY = bounds.y - wt.ty;

        CacheAsBitmap._tempMatrix.a = wt.a;
        CacheAsBitmap._tempMatrix.b = wt.b;
        CacheAsBitmap._tempMatrix.c = wt.c;
        CacheAsBitmap._tempMatrix.d = wt.d;
        CacheAsBitmap._tempMatrix.tx = Math.round(wt.tx - bounds.x);
        CacheAsBitmap._tempMatrix.ty = Math.round(wt.ty - bounds.y);

        cache.render( self.gameObject.phaser, CacheAsBitmap._tempMatrix, true);
        wt = sprite.worldTransform;
        wt.identity();
        wt.tx = bounds.x;
        wt.ty = bounds.y;
    }
    self._updatingCache = false;
    self.dirty = false;
    self.gameObject._isTransformDirty = true;

    // cache 的图片往屏幕上绘制的时候关闭 smooth 处理
    cache.baseTexture.scaleMode = PIXI.scaleModes.NEAREST;
};

/**
 * 检测是否需要重新缓存
 */
CacheAsBitmap.prototype.postUpdate = function() {
    var self = this,
        sprite = self._cacheSprite; 
    if (self.dirty) {
        self.game.log.trace('[CacheAsBitmap]Update Cache.');
        self._updateCache();
    }
    else if (self._cacheType !== CacheAsBitmap.CACHE_FOR_SCREEN) {
        var swt = sprite.worldTransform,
            wt = self.gameObject.worldTransform;
        swt.a = wt.a;
        swt.b = wt.b;
        swt.c = wt.c;
        swt.d = wt.d;
        swt.tx = wt.tx;
        swt.ty = wt.ty;
    }
    else {
    	var gwt = self.gameObject.worldTransform;
        var wt = sprite.worldTransform;
        wt.identity();
        wt.tx = gwt.tx + self.offX;
        wt.ty = gwt.ty + self.offY;
    }
};

/**
 * 获取需要缓存的范围
 * @returns {*}
 */
CacheAsBitmap.prototype.getBounds = function() {
    var self = this;
    if (self.boundsType === CacheAsBitmap.BOUNDS_SELF) {
        return qc.Bounds.getBox(self.gameObject, qc.Bounds.USE_BOUNDS, true, 0);
    }
    else if (self.boundsType === CacheAsBitmap.BOUNDS_ALL) {
        return qc.Bounds.getBox(self.gameObject, qc.Bounds.USE_BOUNDS, true, -1);
    }
    else {
        return new qc.Rectangle(0, 0, 1, 1);
    }
};

/**
 * 获取需要缓存的本地范围
 * @returns {*}
 */
CacheAsBitmap.prototype.getLocalBounds = function() {
    var self = this;
    if (self.boundsType === CacheAsBitmap.BOUNDS_SELF) {
        return qc.Bounds.getBox(self.gameObject, qc.Bounds.USE_BOUNDS, true, 0, self.gameObject);
    }
    else if (self.boundsType === CacheAsBitmap.BOUNDS_ALL) {
        return qc.Bounds.getBox(self.gameObject, qc.Bounds.USE_BOUNDS, true, -1, self.gameObject);
    }
    else {
        return new qc.Rectangle(0, 0, 1, 1);
    }
};

/**
 * 在Canvas下的绘制
 * @param renderSession
 */
CacheAsBitmap.prototype.renderCanvas = function(renderSession) {
    var self = this, 
        go = self.gameObject;
    if (go.phaser.visible === false || go.renderable === false)
        return;
    if (self._updatingCache && self._nodeRenderCanvas) {
        self._nodeRenderCanvas.call(self.gameObject.phaser, renderSession);
        return;
    }
    self._cacheSprite._renderCanvas(renderSession);
};

/**
 * 在WebGL下的绘制
 * @param renderSession
 */
CacheAsBitmap.prototype.renderWebGL = function(renderSession){
    var self = this, 
        go = self.gameObject;
    if (go.phaser.visible === false || go.renderable === false)
        return;
    if (self._updatingCache && self._nodeRenderWebGL) {
        self._nodeRenderWebGL.call(self.gameObject.phaser, renderSession);
        return;
    }
    self._cacheSprite._renderWebGL(renderSession);
};

/**
 * 临时矩阵缓存
 * @type {PIXI.Matrix}
 * @private
 */
CacheAsBitmap._tempMatrix = new PIXI.Matrix();

/**
 * 拓展Node的属性
 */
Object.defineProperties(qc.Node.prototype, {

    /**
     * @property {boolean} cacheAsBitmap - 缓存为图片，一提升效率
     */
    cacheAsBitmap: {
        get: function()  {
            var cacheAsBitmap = this.getScript('qc.CacheAsBitmap');
            return cacheAsBitmap && cacheAsBitmap.enable;
        },
        set: function(v) {
            var cacheAsBitmap = this.getScript('qc.CacheAsBitmap');
            if (!v && !cacheAsBitmap) {
                return;
            }
            if (!cacheAsBitmap) {
                cacheAsBitmap = this.addScript('qc.CacheAsBitmap');
            }
            cacheAsBitmap.enable = v;
        }
    }
});

/**
 * 使用自己实际显示范围作为边框范围，进行缓存
 * @constant
 * @type {number}
 */
CacheAsBitmap.BOUNDS_SELF = 0;

/**
 * 使用自己和所有可显示子节点的实际显示范围作为边框范围，进行缓存
 * @constant
 * @type {number}
 */
CacheAsBitmap.BOUNDS_ALL = 1;

/**
 * 相对于屏幕进行缓存，当缓存节点中有文本显示时，建议使用此模式
 */
CacheAsBitmap.CACHE_FOR_SCREEN = 0;
/**
 * 相对于自身节点进行缓存，当自身进行缩放，旋转时，不用更新缓存
 */
CacheAsBitmap.CAHCE_FOR_SELF = 1;
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 调试信息查看器
 * @class qc.DebugViewer
 */
var DebugViewer = defineBehaviour('qc.DebugViewer', qc.Behaviour, function() {
        var self = this;

        // 调度次数，开始计算的时间点等
        self.count = 1;
        self.now = self.game.time.now;
        self.runInEditor = true;

        // 多久统计1次，单位为秒
        self.duration = 1;
    },
    {
        debugOn: qc.Serializer.BOOLEAN,
        duration: qc.Serializer.NUMBER
    }
);

// 菜单上的显示
DebugViewer.__menu = 'Debug/DebugViewer';

Object.defineProperties(DebugViewer.prototype, {
    /**
     * @property {boolean} debugOn - 调试开关是否开启
     */
    debugOn: {
        get: function()  { return this.game.debug.on; },
        set: function(v) { this.game.debug.on = v;    }
    }
});

DebugViewer.prototype.postUpdate = function() {
    var debug = this.game.debug;
    var now = this.game.time.now;
    if (now - this.now >= this.duration * 1000) {
        // 超过1s，进行计算
        var frame = this.count * 1000 / (now - this.now);
        var preUpdate = (debug.preUpdate / this.count).toFixed(1);
        var update = (debug.update / this.count).toFixed(1);
        var postUpdate = (debug.postUpdate / this.count).toFixed(1);
        var logic = (debug.logic / this.count).toFixed(1);
        var render = (debug.render / this.count).toFixed(1);
        var total = (debug.total / this.count).toFixed(1);

        var drawCalls = this.game.phaser.renderer.renderSession.drawCount;
        if (drawCalls == null) {
            drawCalls = '(N/A)';
        }

        var wtCalcCount = this.game.phaser._lastCalcTransformCount;

        var text =
            'FPS: ' + frame.toFixed(1) + '\n' +
            'Draw Calls: ' + drawCalls + '\n' +
            'Total: ' + total + ' ms\n' +
            'Logic: ' + logic + ' ms\n' +
            'Render: ' + render + ' ms\n' +
            'PreUpdate: ' + preUpdate + ' ms\n' +
            'Update: ' + update + ' ms\n' +
            'PostUpdate: ' + postUpdate + ' ms\n' +
            'TransformCalc: ' + wtCalcCount + '';

        if (this.gameObject instanceof qc.UIText)
            this.gameObject.text = text;
        else if (this.gameObject instanceof qc.Dom) {
            text = text.replace(/\n/g, '<br/>');
            this.gameObject.innerHTML = text;
        }

        // 重置下
        this.count = 1;
        this.now = now;
        debug.total = 0;
        debug.logic = 0;
        debug.preUpdate = 0;
        debug.update = 0;
        debug.postUpdate = 0;
        debug.render = 0;
    }
    else {
        this.count++;
    }
};

/**
 * Created by qcplay on 7/8/15.
 */

/**
 * 着色器支持管理类
 * 用来控制值节点的着色器操作
 */

var FilterGroup = defineBehaviour('qc.FilterGroup', qc.Behaviour, function() {
    /**
     * @property {[qc.Filter]} _filters - 当前使用的着色器
     * @private
     */
    this._filters = [];
}, {
    filters : Serializer.FILTERS,
    inherited : Serializer.BOOLEAN
});

// 菜单上的显示
FilterGroup.__menu = 'UI/FilterGroup';

Object.defineProperties(FilterGroup.prototype,{
    /**
     * @property {qc.Filter} filters - 当前使用的所有着色器
     */
    filters : {
        get : function() { return this._filters; },
        set : function(v) {
            this._filters = v;
            this.refresh();
        }
    },

    /**
     * @property {boolean}  inherited - 是否被子节点继承
     */
    inherited : {
        get : function() {
            return !(this.gameObject && this.gameObject.filterSelf);
        },
        set : function(v) {
            this.gameObject && (this.gameObject.filterSelf = !v);
        }
    }
});

/**
 * 禁用
 */
FilterGroup.prototype.onDisable = function() {
    if (!this.gameObject || this.gameObject._destroy)
        return;
    this.refresh();
};

/**
 * 启用
 */
FilterGroup.prototype.onEnable = function() {
    if (!this.gameObject || this.gameObject._destroy)
        return;
    this.refresh();
};

/**
 * 销毁
 */
FilterGroup.prototype.onDestroy = function() {
    if (!this.gameObject || this.gameObject._destroy)
        return;
    this.gameObject.phaser && (this.gameObject.phaser.filters = null);
};

/**
 * 刷新当前着色器状态
 */
FilterGroup.prototype.refresh = function() {
    if (!this.gameObject || this.gameObject._destroy) {
        return;
    }
    if (!this.enable) {
        this.gameObject.phaser.filters = null;
    }
    else if (this.enable) {
        this.gameObject.phaser.filters = this.filters && this.filters.length > 0 ? this.filters : null;
    }
};

/**
 * 根据类型查找着色器
 * @param cls {string|qc.Filter} - 需要查找的着色器类名
 * @return {[qc.Filter]}
 */
FilterGroup.prototype.findFilter = function(cls) {
    if (typeof cls === 'string')
        cls = qc.Util.findClass(cls);
    var filters = [];
    for (var c in this.filters) {
        if (this.filters[c] instanceof cls) {
            filters.push(this.filters[c]);
        }
    }
    return filters;
};

/**
 * 移除一个指定位置的着色器
 * @param filter {number}
 */
FilterGroup.prototype.removeFilterAt = function(idx) {
    if (idx < 0 || idx >= this.filters.length) {
        return;
    }
    var filter = this.filters.splice(idx, 1);
    this.refresh();
    return filter;
};

/**
 * 根据类型或者对象删除着色器
 * @param obj {string|qc.Filter|object} - 需要删除的着色器
 */
FilterGroup.prototype.removeFilter = function(obj) {
    if (typeof obj === 'string') {
        obj = qc.Util.findClass(obj);
    }
    var remove = [];
    var idx = this.filters.length;
    if (typeof obj === 'function') {
        while (idx-- > 0) {
            if (this.filters[idx] instanceof obj) {
                remove.push(this.filters.splice(idx, 1));
            }
        }
    }
    else {
        while (idx-- > 0) {
            if (this.filters[idx] === obj) {
                remove.push(this.filters.splice(idx, 1));
            }
        }
    }
    this.refresh();
    return remove;
};

/**
 * 添加一个着色器
 * @param obj {string|qc.Filter|object} - 需要添加着色器类或者对象
 * @param idx {null|number} - 需要添加到的位置
 */
FilterGroup.prototype.addFilter = function(obj, idx) {
    if (typeof obj === 'string') {
        obj = qc.Util.findClass(obj);
        if (typeof obj !== 'function') {
            return null;
        }
    }

    if (typeof obj === 'function') {
        obj = new obj();
    }
    else {
        var idx = this.filters.length;
        while (idx-- > 0) {
            if (this.filters[idx] === obj) {
                // 已存在该对象
                return;
            }
        }
    }

    if (isNaN(idx) || idx < 0 || idx > this.filters.length) {
        this.filters.push(obj);
    }
    else {
        this.filters.splice(idx, 0, obj);
    }

    this.refresh();
    return obj;
};
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 水平布局
 * 拓展TableLayout
 */
var HorizontalLayout = defineBehaviour('qc.HorizontalLayout', qc.TableLayout, function() {
    // 初始化参数
    this.style = qc.TableLayout.STYLE_RESIZE_ELEMENT;
    this.constraint = qc.TableLayout.CONSTRAINT_FIX_ROW_COUNT;
    this.contentSizeProvider = qc.TableLayout.USE_RECTTRANSFORM;
    this.stride = 1;
    this.startCorner = qc.TableLayout.CORNER_TOP_LEFT;
    this.startAxis = qc.TableLayout.AXIS_HORIZONTAL;
},{
    childForceExpandWidth : qc.Serializer.NUMBER,
    childForceExpandHeight : qc.Serializer.NUMBER
});

// 菜单上的显示
HorizontalLayout.__menu = 'UI/Layout/HorizontalLayout';

Object.defineProperties(HorizontalLayout.prototype, {
    /**
     * @property {number} spacing - 布局间距
     */
    spacing : {
        get : function() { return this.spacingX; },
        set : function(value) { this.spacingX = value; }
    },

    /**
     * @property {number} childForceExpandWidth - 是否强制拉伸宽度
     */
    childForceExpandWidth : {
        get : function() { return !!this._childForceExpandWidth; },
        set : function(value) {
            if (this._childForceExpandWidth === value) {
                return;
            }
            this._childForceExpandWidth = value;
            this.rebuildTable();
        }
    },

    /**
     * @property {number} childForceExpandWidth - 是否强制拉伸高度
     */
    childForceExpandHeight : {
        get : function() { return !!this._childForceExpandHeight; },
        set : function(value) {
            if (this._childForceExpandHeight === value) {
                return;
            }
            this._childForceExpandHeight = value;
            this.rebuildTable();
        }
    }
});

/**
 * 获取在某一轴线上的默认布局系数
 * @override
 * @private
 */
HorizontalLayout.prototype._getDefaultLayout = function(axis){
    if (axis === 'x') {
        return {min : 0,
            preferred : 0,
            flexible : this.childForceExpandWidth ? 1 : 0,
            extra : 0};
    }
    else {
        return {min : 0,
            preferred : this.childForceExpandHeight ? this.gameObject.rect.height : -1,
            flexible : 1,
            extra : 0};
    }

};

/**
 * 获取在某一节点的布局系数
 * @param node {qc.Node} - 节点
 * @returns {*|LayoutElement}
 * @override
 */
HorizontalLayout.prototype.getCellLayout = function(node) {
    var layout = qc.LayoutElement.getLayoutElement(node);
    this.childForceExpandWidth && (layout.flexibleWidth = 1);
    this.childForceExpandHeight && (layout.flexibleHeight = 1);
    return layout;
};
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 以节点rectTransform为裁切路径的遮罩
 * @class qc.NodeMask
 */
var NodeMask = defineBehaviour('qc.NodeMask', qc.Behaviour, function() {
    /**
     * @property {Phaser.Graphics} _mask - 裁切用的graphics
     * @type {Phaser.Graphics}
     * @private
     */
    this._mask = this.gameObject.game.phaser.add.graphics(0, 0);
    if (this._mask.parent) {
        this._mask.parent.removeChild(this._mask);
    }
    // this.gameObject.phaser.addChild(this._mask);

    if (this.gameObject.onTextureChanged) {
        this.gameObject.onTextureChanged.add(this._doTextureChange, this);
    }

    // 默认启用
    this.onEnable();
    this.runInEditor = true;
},{
    checkInField : qc.Serializer.BOOLEAN,
    minAnchor : qc.Serializer.GEOM,
    maxAnchor : qc.Serializer.GEOM,
    minRotation : qc.Serializer.NUMBER,
    maxRotation : qc.Serializer.NUMBER,
    clipType : qc.Serializer.NUMBER,
    mode : qc.Serializer.NUMBER
});

// 菜单上的显示
NodeMask.__menu = 'UI/NodeMask';

Object.defineProperties(NodeMask.prototype, {
    /**
     * @property {boolean} checkInField - 是否检测子节点是否在范围内
     */
    checkInField : {
        get : function() { return !!this._checkInField; },
        set : function(value) {
            if (this._checkInField === value) {
                return;
            }
            this._checkInField = value;
            if (!value) {
                this._resetRenderAble(this.gameObject);
            }
        }
    },

    /**
     * @property {qc.Point} minAnchor
     * @readonly
     * 定位矩形左上角的坐标（相对于gameObject长宽的比例值）
     *  (0, 0) - 左上角
     *  (1, 1) - 右下角
     * 注：大小不能超过maxAnchor
     */
    minAnchor : {
        get : function() {
            return this._minAnchor || new qc.Point(0, 0);
        },
        set : function(v) {
            this.setMask(v);
        }
    },

    /**
     * @property {qc.Point} maxAnchor
     * @readonly
     * 定位矩形右下角的坐标（相对于gameObject长宽的比例值）
     *  (0, 0) - 左上角
     *  (1, 1) - 右下角
     * 注：大小不能小于minAnchor
     */
    maxAnchor : {
        get : function() {
            return this._maxAnchor || new qc.Point(1, 1);
        },
        set : function(v) {
            this.setMask(null, v);
        }
    },

    /**
     * @property {Number} minRotation - 起始的显示角度，x轴正向为0，顺时针旋转
     */
    minRotation : {
        get : function() { return typeof this._minRotation !== 'undefined' || this._minRotation === null ?
            this._minRotation : 0; },
        set : function(value) {
            this.setMask(null, null, value);
        }
    },

    /**
     * @property {Number} maxRotation - 结束的显示角度, 值必须大于等于minRotation，x轴正向为0，顺时针旋转
     */
    maxRotation : {
        get : function() { return typeof this._maxRotation !== 'undefined' || this._maxRotation === null ?
            this._maxRotation : 2 * Math.PI; },
        set : function(value) {
            this.setMask(null, null, null, value);
        }
    },

    /**
     * @property {number} clipType - 裁切方式，只在 WebGl 模式下生效，当前值固定为通过模板测试裁切
     */
    clipType : {
        get : function() { return NodeMask.CLIP_STENCIL; },
        set : function(v) {

        }
    },

    /**
     * @property {number} mode - 裁切模式
     */
    mode : {
        get : function() { return this._mode || NodeMask.MODE_DEFAULT; },
        set : function(v) {
            if (this._mode === v) {
                return;
            }
            this._mode = v;
            this._refresh();
        }
    },

    /**
     * @property {Phaser.Graphics} customGraphics - 自定义的裁切图形
     */
    customGraphics : {
        get : function() {
            if (!this._customGraphics) {
                this._customGraphics = this.game.phaser.add.graphics(0, 0);
                if (this._customGraphics.parent) {
                    this._customGraphics.parent.removeChild(this._customGraphics);
                }
            }
            return this._customGraphics;
        }
    },

    /**
     * @property {qc.Filter.FilterTexture} _maskFilter - 自定义的裁切Filter
     * @private
     */
    _maskFilter : {
        get : function() {
            if (!this.__maskFilter) {
                this.__maskFilter = new qc.Filter.AlphaMask(this.game);
                this.__maskFilter.otherTexture = new qc.Filter.FilterTexture(this.game);
                this.__maskFilter.otherTexture.texture = this.gameObject.texture;
                this.__maskFilter.otherTexture.frame = this.gameObject.frame;
            }
            return this.__maskFilter;
        }
    },

    /**
     * @property {qc.Filter.GraphicsTexture} _graphicsFilter - 自定义的图形裁切Filter
     * @private
     */
    _graphicsFilter : {
        get : function() {
            if (!this.__graphicsFilter) {
                this.__graphicsFilter = new qc.Filter.AlphaMask(this.game);
                this.__graphicsFilter.otherTexture = new qc.Filter.GraphicsTexture(this.game);
            }
            return this.__graphicsFilter;
        }
    }
});

/**
 * 启用
 */
NodeMask.prototype.onEnable = function() {
    this._refresh();
};

/**
 * 停用
 */
NodeMask.prototype.onDisable = function() {
    this._refresh();
};

/**
 * 销毁时
 */
NodeMask.prototype.onDestroy = function() {
    this.gameObject.phaser.mask = null;
    this.gameObject.phaser.softClip = null;
    this.gameObject.phaser.maskPixel = null;
    this.gameObject.phaser._graphicsFilter = null;
    if (this.gameObject.onTextureChanged) {
        this.gameObject.onTextureChanged.remove(this._doTextureChange, this);
    }
    if (!this.gameObject._destroy) {
        this._resetRenderAble(this.gameObject);
    }
};

/**
 * 当贴图变化时
 */
NodeMask.prototype._doTextureChange = function() {
    if (this.__maskFilter) {
        this.__maskFilter.otherTexture.texture = this.gameObject.texture;
        this.__maskFilter.otherTexture.frame = this.gameObject.frame;
    }
};

NodeMask.prototype._isNeedUseClipReplaceStencil = function() {
    return !this.game.device.phaser.supportStencil && 
        this.game.phaser.renderType === Phaser.WEBGL && 
        this.clipType === NodeMask.CLIP_STENCIL;
};

/**
 * 刷新绘制对象的 mask 信息
 * @private
 */
NodeMask.prototype._refresh = function() {
    if (this._isNeedUseClipReplaceStencil()) {
        this.gameObject.phaser.softClip = null;
        this.gameObject.phaser.mask = null;
        this.gameObject.phaser.maskPixel = null;
        this._graphicsFilter.otherTexture.updateTexture();
        this.gameObject.phaser._graphicsFilter = this.enable ? 
            { target: this.gameObject.phaser, filterPasses: this._graphicsFilter.passes} :
            null;
    }
    else if (this.game.phaser.renderType !== Phaser.WEBGL ||
        this.clipType === NodeMask.CLIP_STENCIL) {
        this.gameObject.phaser.mask = this.enable && this.mode !== NodeMask.MODE_PIXEL ?
            (this.mode === NodeMask.MODE_GRAPHICS ? this.customGraphics : this._mask) : null;
        this.gameObject.phaser.maskPixel = this.enable && this.mode === NodeMask.MODE_PIXEL ?
            { target: this.gameObject.phaser, filterPasses:this._maskFilter.passes } : null;
        this.gameObject.phaser.softClip = null;
        if (!this.gameObject._destroy) {
            this._resetRenderAble(this.gameObject);
        }
        this.gameObject.phaser._clipTexture = null;
        this.gameObject.phaser._graphicsFilter = null;
    }
    else {
        this.gameObject.phaser.softClip = this.enable && this.mode !== NodeMask.MODE_PIXEL ? this._softClip : null;
        this.gameObject.phaser.maskPixel = this.enable && this.mode === NodeMask.MODE_PIXEL ?
            { target: this.gameObject.phaser, filterPasses:this._maskFilter.passes } : null;
        this.gameObject.phaser.mask = null;
        this.gameObject.phaser._clipTexture = null;
        this.gameObject.phaser._graphicsFilter = null;
    }
};

/**
 * postUpdate
 */
NodeMask.prototype.postUpdate = function() {
    // 启用状态下更新mask
    if (this.enable && this.mode === NodeMask.MODE_DEFAULT) {
        this._updateMask();

        if (this._checkInField) {
            // 计算子节点是否越界
            var children = this.gameObject.children;
            var worldBox = this.gameObject.getWorldBox();
            for (var i = 0; i < children.length; i++)
            {
                this._checkRenderable(worldBox, children[i]);
            }
        }
    }
};

/**
 * 设置裁切的变化信息
 * @param minAnchor {qc.Point} - 定位裁切矩形左上角的坐标
 * @param maxAnchor {qc.Point} - 定位裁切矩形右下角的坐标
 * @param minRotation {Number} - 起始的角度
 * @param maxRotation {Number} - 结束的角度
 */
NodeMask.prototype.setMask = function(minAnchor, maxAnchor, minRotation, maxRotation) {
    var min = minAnchor || this.minAnchor;
    var max = maxAnchor || this.maxAnchor;
    minRotation = typeof minRotation === 'undefined' || minRotation === null ? this.minRotation : minRotation;
    maxRotation = typeof maxRotation === 'undefined' || maxRotation === null ? this.maxRotation : maxRotation;
    if (min.x < 0) min.x = 0;
    if (min.y < 0) min.y = 0;
    if (min.x > 1) min.x = 1;
    if (min.y > 1) min.y = 1;
    if (max.x < 0) max.x = 0;
    if (max.y < 0) max.y = 0;
    if (max.x > 1) max.x = 1;
    if (max.y > 1) max.y = 1;
    if (min.x > max.x || min.y > max.y)
        throw new Error('Expected:min < max');
    if (maxRotation < minRotation)
        throw new Error('Expected:minRotation < maxRotation');

    if (min.x === this.minAnchor.x && min.y === this.minAnchor.y &&
        max.x === this.maxAnchor.x && max.y === this.maxAnchor.y &&
        minRotation === this.minRotation && maxRotation === this.maxRotation) {
        return;
    }

    this._minAnchor = min;
    this._maxAnchor = max;
    this._minRotation = minRotation;
    this._maxRotation = maxRotation;
    this._maskForceChange = true;
};

/**
 * 重置遮罩
 * @private
 */
NodeMask.prototype._updateMask = function() {
    var mask = this._mask;
    var worldTransform = this.gameObject.worldTransform;

    var rect = this.gameObject.rect;
    // 判定裁切信息是否有变化
    var transInfo = [rect.x, rect.y, rect.width, rect.height,
        worldTransform.a, worldTransform.b, worldTransform.c, worldTransform.d,
        worldTransform.tx, worldTransform.ty];
    if (!this._maskForceChange &&
        this._lastMaskRect &&
        this._lastMaskRect[0] === transInfo[0] &&
        this._lastMaskRect[1] === transInfo[1] &&
        this._lastMaskRect[2] === transInfo[2] &&
        this._lastMaskRect[3] === transInfo[3] &&
        this._lastMaskRect[4] === transInfo[4] &&
        this._lastMaskRect[5] === transInfo[5] &&
        this._lastMaskRect[6] === transInfo[6] &&
        this._lastMaskRect[7] === transInfo[7] &&
        this._lastMaskRect[8] === transInfo[8] &&
        this._lastMaskRect[9] === transInfo[9])
    {
        return;
    }
    this._maskForceChange = false;
    this._lastMaskRect = transInfo;

    var minAnchor = this.minAnchor;
    var maxAnchor = this.maxAnchor;
    var minRotation = this.minRotation;
    var maxRotation = this.maxRotation;

    var minX = rect.x + minAnchor.x * rect.width;
    var minY = rect.y + minAnchor.y * rect.height;
    var maxX = rect.x + maxAnchor.x * rect.width;
    var maxY = rect.y + maxAnchor.y * rect.height;

    // 负值的缩放会改变顶点间的顺序，需要进行调整
    var scale = this.gameObject.getWorldScale();
    var needReverse = scale.x * scale.y < 0;

    var centerX = (minX + maxX) / 2;
    var centerY = (minY + maxY) / 2;
    var vertexes = null;
    if (maxRotation - minRotation >= Math.PI * 2) {
        // 围绕一圈了
        vertexes = [
            new qc.Point(minX, minY), 
            new qc.Point(maxX, minY),
             new qc.Point(maxX, maxY), 
             new qc.Point(minX, maxY)];
    }
    else {
        var PI2 = 2 * Math.PI;
        var corner = Math.atan2(maxY - minY, maxX - minX);
        while (minRotation < -corner || minRotation > PI2 - corner) {
            var add = minRotation < 0 ? PI2 : -PI2;
            minRotation += add;
            maxRotation += add;
        }

        var startX, startY, startCorner;
        if (minRotation >= -corner && minRotation < corner) {
            startX = maxX;
            startY = centerY + (maxX - centerX) * Math.tan(minRotation);
            startCorner = 0;
        }
        else if (minRotation < Math.PI - corner) {
            startY = maxY;
            startX = centerX + (maxY - centerY) / Math.tan(minRotation);
            startCorner = 1;
        }
        else if (minRotation < Math.PI + corner) {
            startX = minX;
            startY = centerY + (centerX - minX) * Math.tan(Math.PI - minRotation);
            startCorner = 2;
        }
        else {
            startY = minY;
            startX = centerX + (centerY - minY) / Math.tan(PI2 - minRotation);
            startCorner = 3;
        }

        var endX, endY, endCorner = 0;
        while (maxRotation > PI2 - corner) {
            maxRotation -= PI2;
            endCorner += 4;
        }

        if (maxRotation >= -corner && maxRotation < corner) {
            endX = maxX;
            endY = centerY + (maxX - centerX) * Math.tan(maxRotation);
            endCorner += 0;
        }
        else if (maxRotation < Math.PI - corner) {
            endY = maxY;
            endX = centerX + (maxY - centerY) / Math.tan(maxRotation);
            endCorner += 1;
        }
        else if (maxRotation < Math.PI + corner) {
            endX = minX;
            endY = centerY + (centerX - minX) * Math.tan(Math.PI - maxRotation);
            endCorner += 2;
        }
        else {
            endY = minY;
            endX = centerX + (centerY - minY) / Math.tan(PI2 - maxRotation);
            endCorner += 3;
        }

        var corners = [new qc.Point(maxX, maxY), new qc.Point(minX, maxY),
            new qc.Point(minX, minY), new qc.Point(maxX, minY)];
        vertexes = [new qc.Point(centerX, centerY), new qc.Point(startX, startY)];
        while (startCorner < endCorner) {
            vertexes.push(corners[startCorner % 4]);
            startCorner ++;
        }
        vertexes.push(new qc.Point(endX, endY));
    }

    if (!this._isNeedUseClipReplaceStencil()) {
        for (var i = 0; i < vertexes.length; i++) {
            vertexes[i] = worldTransform.apply(vertexes[i]);
        }

        needReverse && vertexes.reverse();
    }

    this._softClip = vertexes;
    if (this._isNeedUseClipReplaceStencil()) {
        var bounds = this.gameObject.phaser.getBounds();
        var texture = this._graphicsFilter.otherTexture;
        texture.resize(bounds.width, bounds.height);
        texture.offset.x = -bounds.x;
        texture.offset.y = -bounds.y;
        var g = texture.graphics;
        g.clear();
        g.beginFill(0xffffff);
        g.drawPolygon(vertexes);
        g.worldTransform = this.gameObject.worldTransform;
        // if (!this._clipTexture) {
        //     this._clipTexture = new qc.RenderTexture(bounds.width, bounds.height, this.game.phaser.render, null, this.game.resolution);
        // }
        // else {
        //     this._clipTexture.resize(bounds.width, bounds.height, true);
        // }
        // mask.worldTransform = this.gameObject.worldTransform;
        // this._clipTexture.directRenderWebGL(mask, {x: -bounds.x, y: -bounds.y}, true);
    }
    else {
        mask.clear();
        mask.beginFill(0xffffff);
        mask.drawPolygon(vertexes);
    }
    this._refresh();

};

/**
 * 检测子节点是否可以绘制
 * @param showBox {qc.Rectangle} - 显示区域
 * @param node {qc.Node} - 节点
 * @private
 */
NodeMask.prototype._checkRenderable = function(showBox, node) {
    var worldBox = node.getWorldBox();
    if (!qc.Rectangle.intersects(showBox, worldBox)) {
        node.renderable = false;
    }
    else {
        node.renderable = true;
    }

    var children = node.children;
    for (var i = 0; i < children.length; i++)
    {
        this._checkRenderable(showBox, children[i]);
    }
};

/**
 * 重置节点为可绘制的
 * @param node {qc.Node} - 节点
 * @private
 */
NodeMask.prototype._resetRenderAble = function(node) {
    if (!node)
        return;
    node.renderable = true;

    var children = node.children;
    for (var i = 0; i < children.length; i++)
    {
        this._resetRenderAble(children[i]);
    }
};

/**
 * 默认裁切源，以当前节点的显示矩形进行裁切
 * @constant
 * @type {number}
 */
NodeMask.MODE_DEFAULT = 0;

/**
 * 以当前节点的显示内容进行裁切
 * @constant
 * @type {number}
 */
NodeMask.MODE_PIXEL = 1;

/**
 * 以自定义的图形进行裁切
 * @constant
 * @type {number}
 */
NodeMask.MODE_GRAPHICS = 2;

/**
 * 使用模拟方式进行裁切
 * 注意：当前实现中软裁切，计算量比较大，性能消耗严重，并对部分自定义renderWebGL的节点支持有问题，暂时进行屏蔽
 * @constant
 * @type {number}
 */
//NodeMask.CLIP_SOFT = 0;
/**
 * 使用WebGl 模本测试进行裁切
 * @constant
 * @type {number}
 */
NodeMask.CLIP_STENCIL = 1;
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 脚本不存在时的替代品
 * @class qc.NonexistScript
 */
var NonexistScript = defineBehaviour('qc.NonexistScript', qc.Behaviour, function() {
    // 丢失的脚本类名
    this.script = '';
    
    // 脚本数据
    this.data = {};
},{
});
NonexistScript.__hiddenInMenu = true;

/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 负责处理游戏的缩放适配
 * @class qc.ScaleAdapter
 */
var ScaleAdapter = defineBehaviour('qc.ScaleAdapter', qc.Behaviour, function() {
    /**
     * @property {qc.Node} target - 缩放的目标Node
     */
    this.target = null;

    /**
     * @property {qc.Point} referenceResolution - 使用的分辨率
     */
    this.referenceResolution = new qc.Point(0, 0);

    /**
     * @property {number} manualType - 缩放类型
     */
    this.manualType = ScaleAdapter.NONE;

    /**
     * @property {boolean} fullTarget - 是否使内容填满目标，通常自身是容器时使用;
     * @private
     */
    this._fullTarget = false;

    /**
     * @property {[number]} gameObjectRecord - 节点的状态记录
     */
    this.gameObjectRecord = this.gameObject ?
        [this.gameObject.scaleX, this.gameObject.scaleY, this.gameObject.width, this.gameObject.height] :
        [];

    // 在编辑器模式下需要运行
    this.runInEditor = true;

    // 菜单上的显示
    this.__menu = 'UI/ScaleAdapter';
}, {
    target : qc.Serializer.NODE,
    referenceResolution : qc.Serializer.GEOM,
    manualType : qc.Serializer.NUMBER,
    fullTarget : qc.Serializer.BOOLEAN,
    gameObjectRecord : qc.Serializer.NUMBERS
});

// 菜单上的显示
ScaleAdapter.__menu = 'UI/ScaleAdapter';

Object.defineProperties(ScaleAdapter.prototype,{
    /**
     * @property {boolean} fullTarget - 是否使内容填满目标，通常自身是容器时使用;
     */
    fullTarget : {
        get : function() { return this._fullTarget; },
        set : function(value) {
            if (this._fullTarget != value) {
                this._fullTarget = value;
                if (!value && this.gameObject) {
                    // 还原下自己节点的大小
                    this.gameObject.width = this.gameObjectRecord[2];
                    this.gameObject.height = this.gameObjectRecord[3];
                }
                this._forceUpdate = true;
            }
        }
    }
});

/**
 * 不进行缩放
 *
 * @constant
 * @type {integer}
 */
ScaleAdapter.NONE = 0;

/**
 * 将基于高度的进行缩放，缩放后的坐标系中目标区域高度为refHeight
 *
 * @constant
 * @type {integer}
 */
ScaleAdapter.MANUAL_HEIGHT = 1;

/**
 * 将基于宽度的进行缩放，缩放后的坐标系中目标区域宽度为refWidth
 *
 * @constant
 * @type {integer}
 */
ScaleAdapter.MANUAL_WIDTH = 2;

/**
 * 将参考矩阵的宽高采用同样缩放比例，使其能完全的放置在目标区域中
 *
 * @constant
 * @type {integer}
 */
ScaleAdapter.EXPAND = 3;

/**
 * 将参考矩阵的宽高采用同样缩放比例，使其能占满整个目标区域
 *
 * @constant
 * @type {integer}
 */
ScaleAdapter.SHRINK = 4;

/**
 * 将参考矩阵的宽高采用不同的缩放比例，使其能完全的占满整个目标区域
 *
 * @constant
 * @type {integer}
 */
ScaleAdapter.FILL = 5;

/** 获取参考对象的大小
 * @method getTargetSize
 * @returns {qc.Point}
 * @internal
 */
ScaleAdapter.prototype.getTargetSize = function() {
    var currTarget = this.target || this.gameObject.game.world;
    if (!currTarget || !currTarget.width || !currTarget.height)
        return new qc.Point(0, 0);
    return new qc.Point(currTarget.width, currTarget.height);
};

/**
 * @method preUpdate
 * @internal
 */
ScaleAdapter.prototype.preUpdate = function() {
    // 自身对象不存在或者不支持缩放时跳过
    if (!this.enable ||
        !this.gameObject ||
        !this.gameObject.scaleX)
        return;

    var targetSize = this.getTargetSize();
    var refSize = this.getReferenceResolution();

    // 参考对象或者参考尺寸不存在时不进行处理
    if (targetSize.x === 0 ||
        targetSize.y === 0 ||
        refSize.x === 0 ||
        refSize.y === 0)
        return;

    var calcScaleX = 1;
    var calcScaleY = 1;
    switch (this.manualType) {
        case qc.ScaleAdapter.MANUAL_HEIGHT:
            calcScaleY = calcScaleX = targetSize.y / refSize.y;
            break;
        case qc.ScaleAdapter.MANUAL_WIDTH:
            calcScaleY = calcScaleX = targetSize.x / refSize.x;
            break;
        case qc.ScaleAdapter.EXPAND:
            calcScaleY = calcScaleX = Math.min(targetSize.x / refSize.x,
                targetSize.y / refSize.y);
            break;
        case qc.ScaleAdapter.SHRINK:
            calcScaleY = calcScaleX = Math.max(targetSize.x / refSize.x,
                targetSize.y / refSize.y);
            break;
        case qc.ScaleAdapter.FILL:
            calcScaleX = targetSize.x / refSize.x;
            calcScaleY = targetSize.y / refSize.y;
            break;
    }

    if (isNaN(calcScaleX) || isNaN(calcScaleY))
        return;

    if (this.fullTarget) {
        this.gameObject.width = targetSize.x / calcScaleX;
        this.gameObject.height = targetSize.y / calcScaleY;
    }
    if (this._forceUpdate ||
        this.gameObject.scaleX !== calcScaleX ||
        this.gameObject.scaleY !== calcScaleY) {
        this._forceUpdate = false;
        this.gameObject.scaleX = calcScaleX;
        this.gameObject.scaleY = calcScaleY;
    }
};

/**
 * 生效
 */
ScaleAdapter.prototype.onEnable = function() {
    this.gameObjectRecord[0] = this.gameObject.scaleX;
    this.gameObjectRecord[1] = this.gameObject.scaleY;
    this.gameObjectRecord[2] = this.gameObject.width;
    this.gameObjectRecord[3] = this.gameObject.height;
};

/**
 * 失效
 */
ScaleAdapter.prototype.onDisable = function() {
    if (this.gameObjectRecord.length < 4 ||
        this.gameObjectRecord[0] === null ||
        isNaN(this.gameObjectRecord[0])) {
        return;
    }
    this.gameObject.scaleX = this.gameObjectRecord[0];
    this.gameObject.scaleY = this.gameObjectRecord[1];
    this.gameObject.width = this.gameObjectRecord[2];
    this.gameObject.height = this.gameObjectRecord[3];
};

/**
 * @method onDestroy
 * @internal
 */
ScaleAdapter.prototype.onDestroy = function() {
    this.target = null;
};

/**
 * @method getReferenceResolution
 * @protected
 */
ScaleAdapter.prototype.getReferenceResolution = function() {
    return this.referenceResolution;
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 单选按钮组，挂载到Node节点后生效
 *
 * @class qc.ToggleGroup
 */
var ToggoleGroup = qc.defineBehaviour('qc.ToggleGroup', qc.Behaviour, function() {
    this._toggles = [];
    this.runInEditor = true;

    /**
     * @property {qc.Signal} onValueChange - 开关组选中状态变化产生的事件
     */
    this.onValueChange = new qc.Signal();
}, {
    toggles : qc.Serializer.NODES
});

// 菜单上的显示
ToggoleGroup.__menu = 'UI/ToggleGroup';

Object.defineProperties(ToggoleGroup.prototype, {
    /**
     * @property {qc.Toggle|Array} toggle - 当前处于开启的开关
     */
    toggle : {
        get : function() {
            for (var i = 0; i<this._toggles.length; i++) {
                var t = this._toggles[i];
                if (t && t.on) {
                    return t;
                }
            }
            return null;
        },
        set : function(v) {
            var index = this._toggles.indexOf(v);
            if (index === -1) {
                this.game.log.error("target is not in this group");
                return;
            }
            for (var i = 0; i<this._toggles.length; i++) {
                var t = this._toggles[i];
                if (t) {
                    t.on = (t === v);    
                }
            }
            this.onValueChange.dispatch(this, v);
        }
    },

    /**
     * @property {array} toggles - 所有的开关列表
     * @readonly
     */
    toggles : {
        get : function() {
            return this._toggles = this._toggles;
        },
        set : function(v) {
            var self = this;
            // 删除老对象的监听
            self._toggles.forEach(function(t) {
                if (t) t.onValueChange.remove(self._onValueChange, self);
            });
            self._toggles = v || [];
            // 添加新对象的监听
            self._toggles.forEach(function(t) {
                if (t) t.onValueChange.add(self._onValueChange, self);
            });
        }
    }
});

/**
 * 添加一个开关到开关组中
 *
 * @method qc.ToggleGroup#add
 * @param toggle
 */
ToggoleGroup.prototype.add = function(toggle) {
    if (this._toggles.indexOf(toggle) !== -1)
        // 已经在组内控制中了
        return;

    // 关注其值的改变，确保开启时其他按钮设置为关闭
    this._toggles.push(toggle);
    
    // 添加进来时，默认都是关闭的状态
    toggle.on = false;    
    
    toggle.onValueChange.add(this._onValueChange, this);
};

/**
 * 将开关从开关组中移除
 *
 * @method qc.ToggleGroup#remove
 * @param toggle
 */
ToggoleGroup.prototype.remove = function(toggle) {
    var index = this._toggles.indexOf(toggle);
    if (index === -1)
        // 开关不在组中，不需要移除
        return;

    this._toggles.splice(index, 1);
    toggle.onValueChange.remove(this._onValueChange, this);
};

/**
 * 关注开关变化的回调
 *
 * @param toggle
 * @private
 */
ToggoleGroup.prototype._onValueChange = function(toggle) {
    if (toggle.on) {
        this.toggle = toggle;
    }
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 交互元素的表现：普通、按下、不可用
 * @class qc.TransitionBehaviour
 */
var TransitionBehaviour = qc.defineBehaviour('qc.TransitionBehaviour', qc.Behaviour, function() {
    if (this.gameObject.state === undefined) {
        throw new Error('The target node does not have the interactive function, and can not be added to this component.');
        return;
    }

    this.target = this.gameObject;
    this._transition = qc.Transition.TEXTURE_SWAP;
},
{
    target : qc.Serializer.NODE,
    _transition : qc.Serializer.NUMBER,
    _normalTexture : qc.Serializer.AUTO,
    _pressedTexture : qc.Serializer.AUTO,
    _disabledTexture : qc.Serializer.AUTO,
    _normalColor : qc.Serializer.COLOR,
    _pressedColor : qc.Serializer.COLOR,
    _disabledColor : qc.Serializer.COLOR
});

// 菜单上的显示
TransitionBehaviour.__menu = 'UI/TransitionBehaviour';

Object.defineProperties(TransitionBehaviour.prototype, {
    /**
     * @property {qc.Node} target - 做表现的目标节点
     */
    target : {
        get : function() { return this._target; },
        set :function(v) {
            this._target = v;
            this._reset();
        }
    },

    /**
     * @property {number} transition - 按钮的切换方式
     * TODO: 动画方式暂时不支持
     */
    transition : {
        get : function() { return this._transition; },
        set : function(v) {
            if (this._transition === v) return;
            this._transition = v;
            if (!this.target) return;
            this._reset();
        }
    },

    /**
     * @property {qc.Texture} normalTexture - 普通状态下的贴图
     */
    normalTexture : {
        get : function() {
            return this._normalTexture;
        },
        set : function(v) {
            if (v === this._normalTexture) return;
            this._normalTexture = v;
            if (!this.target) return;

            // 如果当前处于普通状态，则更新下贴图
            if (this.gameObject.state === qc.UIState.NORMAL &&
                this.transition === qc.Transition.TEXTURE_SWAP)
                this.target.frame = this.normalTexture;
        }
    },

    /**
     * @property {qc.Texture} pressedTexture - 按下的贴图(如果没有指定使用普通状态贴图)
     */
    pressedTexture : {
        get : function() { return this._pressedTexture; },
        set : function(v) {
            if (v === this._pressedTexture) return;
            this._pressedTexture = v;
            if (!this.target) return;

            //  如果当前处于按下状态，则更新下贴图
            if (this.gameObject.state === qc.UIState.PRESSED &&
                this.transition === qc.Transition.TEXTURE_SWAP)
                this.target.frame = this.pressedTexture;
        }
    },

    /**
     * @property {qc.Texture} disabledTexture - 置灰的贴图(如果没有指定使用普通状态贴图)
     */
    disabledTexture : {
        get : function() { return this._disabledTexture; },
        set : function(v) {
            if (v === this._disabledTexture) return;
            this._disabledTexture = v;
            if (!this.target) return;

            //  如果当前处于置灰状态，则更新下贴图
            if (this.gameObject.state === qc.UIState.DISABLED &&
                this.transition === qc.Transition.TEXTURE_SWAP)
                this.target.frame = this.disabledTexture;
        }
    },

    /**
     * @property {qc.Color} normalColor - 普通情况下的混合色，默认为白色
     */
    normalColor : {
        get : function() { return this._normalColor; },
        set : function(v) {
            if (this._normalColor === v) return;
            if (!this.target) return;
            v = v || Color.white;
            if (!(v instanceof Color)) {
                this.game.log.error('Expected qc.Color, got:{0}', v);
                return;
            }
            this._normalColor = v;

            // 如果当前处于普通状态，更新下混合色
            if (this.gameObject.state === qc.UIState.NORMAL &&
                this.transition === qc.Transition.COLOR_TINT)
                this.target.colorTint = this.normalColor;
        }
    },

    /**
     * @property {qc.Color} pressedColor - 按下的混合色
     */
    pressedColor : {
        get : function() { return this._pressedColor; },
        set : function(v) {
            if (this._pressedColor === v) return;
            if (!this.target) return;
            v = v || Color.grey;
            if (!(v instanceof Color)) {
                this.game.log.error('Expected qc.Color, got:{0}', v);
                return;
            }
            this._pressedColor = v;

            // 如果当前处于按下状态，更新下混合色
            if (this.gameObject.state === qc.UIState.PRESSED &&
                this.transition === qc.Transition.COLOR_TINT)
                this.target.colorTint = this.pressedColor;
        }
    },

    /**
     * @property {qc.Color} disabledColor - 组件无效时颜色
     */
    disabledColor : {
        get : function() { return this._disabledColor; },
        set : function(v) {
            if (this._disabledColor === v) return;
            if (!this.target) return;
            v = v || Color.grey;
            if (!(v instanceof Color)) {
                this.game.log.error('Expected:qc.Color, got:{0}', v);
                return;
            }
            this._disabledColor = v;

            // 如果当前处于置灰状态，更新下混合色
            if (this.gameObject.state === qc.UIState.DISABLED &&
                this.transition === qc.Transition.COLOR_TINT)
                this.target.colorTint = this.disabledColor;
        }
    },

    /**
     * @property {string} pressedAnimation - 按下时的动作
     */
    pressedAnimation : {
        get : function() { return this._pressedAnimation; },
        set :function(v) {
            if (this._pressedAnimation === v) return;
            this._pressedAnimation = v;
        }
    }
});

// 默认普通状态的贴图
TransitionBehaviour.prototype._normalTexture = '';

// 默认按下状态的贴图
TransitionBehaviour.prototype._pressedTexture = '';

// 默认无效状态的贴图
TransitionBehaviour.prototype._disabledTexture = '';

// 默认正常混合色
TransitionBehaviour.prototype._normalColor = Color.white;

// 默认按下时混合色
TransitionBehaviour.prototype._pressedColor = Color.grey;

// 默认无效时的混合色
TransitionBehaviour.prototype._disabledColor = Color.grey;

/**
 * 初始化处理
 */
TransitionBehaviour.prototype.awake = function() {
    // 关注状态变更的事件
    this.gameObject.onStateChange.add(this._reset, this);

    // 立刻重新绘制下
    this._reset();
};

/**
 * @method onDestroy
 * @internal
 */
TransitionBehaviour.prototype.onDestroy = function() {
    // 删除状态监听
    this.gameObject.onStateChange.remove(this._reset, this);
};

/**
 * 重新绘制下
 * @private
 */
TransitionBehaviour.prototype._reset = function() {
    if (!this.target) return;
    if (!this.enable) return;

    switch (this.transition) {
    case qc.Transition.NONE:
        // 啥也不干
        break;

    case qc.Transition.TEXTURE_SWAP:
        switch (this.gameObject.state) {
        case qc.UIState.NORMAL:
            if (this.normalTexture)
                this.target.frame = this.normalTexture;
            break;
        case qc.UIState.PRESSED:
            if (this.pressedTexture)
                this.target.frame = this.pressedTexture;
            break;
        case qc.UIState.DISABLED:
            if (this.disabledTexture)
                this.target.frame = this.disabledTexture;
            break;
        }
        break;

    case qc.Transition.COLOR_TINT:
        switch (this.gameObject.state) {
        case qc.UIState.NORMAL:
            this.target.colorTint = this.normalColor; break;
        case qc.UIState.PRESSED:
            this.target.colorTint = this.pressedColor; break;
        case qc.UIState.DISABLED:
            this.target.colorTint = this.disabledColor; break;
        }
        break;

    case qc.Transition.ANIMATION:
        // TODO: 暂时不支持
        break;
    }
}

/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

var Tween = defineBehaviour('qc.Tween', qc.Behaviour, function() {
    /**
     * @property {qc.BezierCurve} _curve - 曲线
     * @private
     */
    this._curve = new qc.BezierCurve(new qc.Keyframe(0, 0, 1, 1), new qc.Keyframe(1, 1, 1, 1));
    /**
     * @property {boolean} _started - 是否已经开始
     * @private
     */
    this._started = false;
    /**
     * @property {number} _startTime - 开始时间
     * @private
     */
    this._startTime = 0;

    /**
     * @property {number} _amountPreDelta - 单位时间的进度值
     * @private
     */
    this._amountPerDelta = 1000;

    /**
     * @property {number} _factor - 取值因子
     * @private
     */
    this._factor = 0;

    // 在编辑器模式下需要运行
    this.runInEditor = true;

    /**
     * @property {string} flag - 标记
     */
    this.flag =  '';

    /**
     * @property {boolean} playOnAwake - 载入时播放
     */
    this.playOnAwake = false;

    /**
     * @property {boolean} canRunInEditor - 是否在 Editor 下运行
     */
    this.canRunInEditor = false;
}, {
    _curve : qc.Serializer.GEOM,
    sampleMethod : qc.Serializer.NUMBER,
    style : qc.Serializer.NUMBER,
    delay : qc.Serializer.NUMBER,
    duration : qc.Serializer.NUMBER,
    tweenGroup : qc.Serializer.NUMBER,
    flag: qc.Serializer.STRING,
    playOnAwake: qc.Serializer.BOOLEAN
});

// 菜单上的隐藏
Tween.__hiddenInMenu = true;

Object.defineProperties(Tween.prototype, {
    /**
     * @property {qc.BezierCurve} curve - 曲线
     */
    curve : {
        get : function() {
            return this._curve;
        }
    },
    /**
     * @property {number} sampleMethod - 时间的采样方式, 默认为线性采样
     */
    sampleMethod : {
        get : function() { return this._sampleMethod || Tween.SAMPLE_LINEAR; },
        set : function(v) {
            if (this._sampleMethod === v) {
                return;
            }
            this._sampleMethod = v;
        }
    },
    /**
     * @property {number} style - 循环类型
     */
    style : {
        get : function() { return this._style || 0; },
        set : function(v) {
            if (this._style === v) return;
            this._style = v;
        }
    },
    /**
     * @property {number} delay - 开始的延时
     */
    delay : {
        get : function() { return (isNaN(this._delay) || this._delay < 0) ? 0 : this._delay; },
        set : function(v) {
            if (this._delay === v) return;
            this._delay = v;
        }
    },
    /**
     * @property {number} duration - 持续时间
     */
    duration : {
        get : function() { return (isNaN(this._duration) || this._duration < 0) ? 0 : this._duration; },
        set : function(v) {
            if (this._duration === v) return;
            this._duration = v;
            this._amountPerDelta = Math.abs(v > 0 ? 1 / (v * 1000) : 1000) * (this._amountPerDelta >= 0 ? 1 : -1 );
        }
    },
    /**
     * @property {number} tweenGroup - 组别
     */
    tweenGroup : {
        get : function() { return this._tweenGroup || 0; },
        set : function(v) {
            if (this._tweenGroup === v) return;
            this._tweenGroup = v;
        }
    },

    /**
     * @property {Phaser.Signal} onStart - 开始事件
     */
    onStart : {
        get: function() {
            if (!this._onStart) {
                this._onStart = new Phaser.Signal();
            }
            return this._onStart;
        }
    },

    /**
     * @property {Phaser.Signal} onFinished -  结束事件
     */
    onFinished : {
        get: function() {
            if (!this._onFinished) {
                this._onFinished = new Phaser.Signal();
            }
            return this._onFinished;
        }
    },

    /**
     * @property {Phaser.Signal} onLoopFinished - 循环一次事件
     */
    onLoopFinished : {
        get : function() {
            if (!this._onLoopFinished) {
                this._onLoopFinished = new Phaser.Signal();
            }
            return this._onLoopFinished;
        }
    },

    /**
     * @property {number} amountPerDelta - 单位时间的进度值
     * @readonly
     */
    amountPerDelta : {
        get : function() { return this._amountPerDelta; }
    },

    /**
     * @property {number} tweenFactor - tween 曲线的因子
     */
    tweenFactor : {
        get : function()  { return this._factor; },
        set : function(v) {
            this._factor = Phaser.Math.clamp(v, 0, 1);
        }
    },

    /**
     * @property {number} direction - 当前行进的方向
     */
    direction : {
        get : function() {
            return this.amountPerDelta <  0 ? Tween.DIR_REVERSE : Tween.DIR_FORWARD;
        }
    }
});

/**
 * 组件初始化处理
 */
Tween.prototype.awake = function() {
    var self = this;
    self.enable = false;

    if (self.playOnAwake) {
        self.resetToBeginning();
        self.playForward();
    }
};

/**
 * 线性采样
 * @param factor
 * @returns {*}
 */
Tween.prototype._lineSample = function(factor) {
    return factor;
};

/**
 * 开始变形
 */
Tween.prototype.start = function() {
    this.update();
};

/**
 * update
 */
Tween.prototype.update = function() {
    if (!this.enable) {
        return;
    }

    if (this.gameObject && this.game.device.editor && !this.canRunInEditor) {
        return;
    }

    var delta = this.game.time.deltaTime;
    var time = this.game.time.scaledTime;
    if (!this._started) {
        this._started = true;
        this._dispatchStart = false;
        this._startTime = time + this.delay * 1000;
    }

    // 未到开始时间，则不进行处理
    if (time < this._startTime) {
        return;
    }
    else if (time === this._startTime) {
        // 刚刚开始，则间隔时间为0
        delta = 0;
    }
    else if (time - this._startTime < delta)
        // 表示有延时情况下第一次开始
        delta = time - this._startTime;

    if (!this._dispatchStart) {
        this._dispatchStart = true;
        if (this._onStart) {
            this._onStart.dispatch(this);
        }
    }

    this._factor += this.amountPerDelta * delta;

    // 处理循环的因子处理
    if (this.style === Tween.STYLE_LOOP) {
        if (this._factor > 1) {
            this._factor -= Math.floor(this._factor);
            if (this._onLoopFinished) {
                this._onLoopFinished.dispatch(this);
            }
        }
    }
    else if (this.style === Tween.STYLE_PINGPONG) {
        if (this._factor > 1) {
            this._factor = 1 - (this._factor - Math.floor(this._factor));
            this._amountPerDelta = -this._amountPerDelta;
            if (this._onLoopFinished) {
                this._onLoopFinished.dispatch(this);
            }
        }
        else if (this._factor < 0) {
            this._factor = -this._factor;
            this._factor -= Math.floor(this._factor);
            this._amountPerDelta = -this._amountPerDelta;
            if (this._onLoopFinished) {
                this._onLoopFinished.dispatch(this);
            }
        }
    }

    if ((this.style === Tween.STYLE_ONCE) && (this.duration === 0 || this._factor > 1 || this._factor < 0)) {
        // 单次播放完成
        this._factor = Phaser.Math.clamp(this._factor, 0, 1);
        this.sample(this._factor, true);

        if (this._onFinished) {
            this._onFinished.dispatch(this);
        }

        if (this.duration === 0 || (this._factor === 1 && this._amountPerDelta > 0 || this._factor === 0 && this._amountPerDelta < 0)) {
            this.enable = false;
        }
    }
    else {
        this.sample(this._factor, false);
    }
};

/**
 * 设置为不可用
 */
Tween.prototype.onDisable = function() {
    this._started = false;
    this.canRunInEditor = false;
};

/**
 * 进行采样
 * @param factor
 */
Tween.prototype.sample = function(factor, isFinished) {
    var val = Phaser.Math.clamp(factor, 0 , 1);
    switch (this.sampleMethod) {
        case Tween.SAMPLE_LINEAR:
            val = this._lineSample(val);
            break;
    }
    this.onUpdate(this.curve.evaluate(val), isFinished);
};


/**
 * 对同组Tween进行操作
 * @param func {function} - 需要进行的操作
 * @private
 */
Tween.prototype._funcGroup = function(func) {
    var self = this;
    if (self.tweenGroup === 0) {
        func(self);
        return;
    }
    Tween._funcGroup(self.gameObject, self.tweenGroup, func);
};

/**
 * 重置当前状态为初始状态
 * @param reverse {boolean} - 是否反向播放的开始
 */
Tween.prototype.reset = function(reverse) {
    if (!this._started) {
        reverse ?
        this.setEndToCurrValue() :
        this.setStartToCurrValue();
    }
};

/**
 * 重置本组Tween当前状态为初始状态
 * @param reverse {boolean} - 是否反向播放的开始
 */
Tween.prototype.resetGroup = function(reverse) {
    var func = function(tween) {
        tween.reset(reverse);
    };
    this._funcGroup(func);
};

/**
 * 正向播放
 */
Tween.prototype.playForward = function() {
    this.play(false);
};

/**
 * 正向播放本组 Tween
 */
Tween.prototype.playGroupForward = function() {
    var func = function(tween) {
        tween.playForward();
    };
    this._funcGroup(func);
};

/**
 * 逆向播放
 */
Tween.prototype.playReverse = function() {
    this.play(true);
};

/**
 * 逆向播放本组 Tween
 */
Tween.prototype.playGroupReverse = function() {
    var func = function(tween) {
        tween.playReverse();
    };
    this._funcGroup(func);
};

/**
 * 停止播放
 */
Tween.prototype.stop = function() {
    this.enable = false;
    this.canRunInEditor = false;
};

/**
 * 停止同组Tween播放
 */
Tween.prototype.stopGroup = function() {
    var func = function(tween) {
        tween.stop();
    };
    this._funcGroup(func);
};


/**
 * 播放
 * @param reverse {boolean} - 是否反向播放
 */
Tween.prototype.play = function(reverse) {
    this._amountPerDelta = Math.abs(this.amountPerDelta) * (reverse ? -1 : 1);
    this.enable = true;
    this.canRunInEditor = true;
    this.update();
};

/**
 * 播放同组Tween
 * @param reverse {boolean} - 是否反向播放
 */
Tween.prototype.playGroup = function(reverse) {
    var func = function(tween) {
        tween.play(reverse);
    };
    this._funcGroup(func);
};

/**
 * 重置为开始状态
 * @param reverse {boolean} - 是否反向播放的开始
 */
Tween.prototype.resetToBeginning = function(reverse) {
    this._started = false;
    this._amountPerDelta = Math.abs(this.amountPerDelta) * (reverse ? -1 : 1);
    this._factor = (this.amountPerDelta < 0) ? 1 : 0;
    this.sample(this._factor, false);
    reverse ? this.setEndToCurrValue() : this.setStartToCurrValue();
};

/**
 * 将同组的Tween重置为开始状态
 * @param reverse {boolean} - 是否反向播放的开始
 */
Tween.prototype.resetGroupToBeginning = function(reverse) {
    var func = function(tween) {
        tween.resetToBeginning(reverse);
    };
    this._funcGroup(func);
};

/**
 * 运行时反向
 */
Tween.prototype.toggle = function() {
    if (this._factor > 0) {
        this._amountPerDelta = -this.amountPerDelta;
    }
    else {
        this._amountPerDelta = Math.abs(this.amountPerDelta);
    }
    this.enable = true;
};

/**
 * 处理对应的形变逻辑
 * @param factor {number} - 形变的因子
 * @param isFinished {boolean} - 是否已经结束
 */
Tween.prototype.onUpdate = function(factor, isFinished) {

};

/**
 * 将开始状态设成当前状态
 */
Tween.prototype.setStartToCurrValue = function() {

};

/**
 * 将结束状态设成当前状态
 */
Tween.prototype.setEndToCurrValue = function() {

};

/**
 * 将当前状态设为开始状态
 */
Tween.prototype.setCurrToStartValue = function() {

};

/**
 * 将当前状态设置为结束状态
 */
Tween.prototype.setCurrToEndValue = function() {

};

/**
 * 开始一个Tween形变
 * @param tweenType {string} - 一个形变类型
 * @param node {qc.Node} - 节点
 * @param duration {number} - 经历的时间
 */
Tween.begin = function(tweenType, node, duration) {
    var tween = node.getScript(tweenType, false);
    if (tween && tween.tweenGroup != 0) {
        tween = null;
        var tweenList = node.getScripts(tweenType, false);
        for (var idx = 0; idx < tweenList.length; ++idx) {
            if (tweenList[idx] && tweenList[idx].tweenGroup === 0) {
                tween = tweenList[idx];
                break;
            }
        }
    }
    if (!tween) {
        tween = node.addScript(tweenType);
        if (!tween) {
            return;
        }
    }

    if (!(tween instanceof Tween)) {
        return;
    }

    tween._started = false;
    tween.duration = duration;
    tween._factor = 0;
    tween._amountPerDelta = Math.abs(tween.amountPerDelta);
    tween.style = Tween.STYLE_ONCE;
    tween._curve = new qc.BezierCurve(new qc.Keyframe(0, 0, 1, 1), new qc.Keyframe(1, 1, 1, 1));
    tween.enable = true;
    return tween;
};

/**
 * 对指定Tween组进行操作
 * @param node {qc.Node} - 需要操作的节点
 * @param groupId {number} - 组 Id
 * @param func {function} - 需要进行的操作
 * @private
 */
Tween._funcGroup = function(node, groupId , func) {
    var tweenList = node.getScripts ? node.getScripts('qc.Tween') : [];
    var tLen = tweenList.length;
    while (tLen-- > 0) {
        if (tweenList[tLen].tweenGroup === groupId) {
            func(tweenList[tLen]);
        }
    }

    var children = node.children;
    if (!children)
        return;
    var len = children.length;
    while (len-- > 0) {
        var sub = children[len];
        Tween._funcGroup(sub, groupId, func);
    }
};

/**
 * 重置本组Tween当前状态为初始状态
 * @param node {qc.Node} - 需要播放动画的节点
 * @param groupId {number} - 需要播放的组Id
 * @param reverse {boolean} - 是否反向播放的开始
 */
Tween.resetGroup = function(node, groupId, reverse) {
    var func = function(tween) {
        tween.reset(reverse);
    };
    Tween._funcGroup(node, groupId, func);
};

/**
 * 正向播放本组Tween
 * @param node {qc.Node} - 需要播放动画的节点
 * @param groupId {number} - 需要播放的组Id
 */
Tween.playGroupForward = function(node, groupId) {
    var func = function(tween) {
        tween.playForward();
    };
    Tween._funcGroup(node, groupId, func);
};

/**
 * 逆向播放本组 Tween
 * @param node {qc.Node} - 需要播放动画的节点
 * @param groupId {number} - 需要播放的组Id
 */
Tween.playGroupReverse = function(node, groupId) {
    var func = function(tween) {
        tween.playReverse();
    };
    Tween._funcGroup(node, groupId, func);
};

/**
 * 停止同组Tween播放
 * @param node {qc.Node} - 需要播放动画的节点
 * @param groupId {number} - 需要播放的组Id
 */
Tween.stopGroup = function(node, groupId) {
    var func = function(tween) {
        tween.stop();
    };
    Tween._funcGroup(node, groupId, func);
};

/**
 * 播放同组Tween
 * @param node {qc.Node} - 需要播放动画的节点
 * @param groupId {number} - 需要播放的组Id
 * @param reverse {boolean} - 是否反向播放
 */
Tween.playGroup = function(node, groupId, reverse) {
    var func = function(tween) {
        tween.play(reverse);
    };
    Tween._funcGroup(node, groupId, func);
};

/**
 * 将同组的Tween重置为开始状态
 * @param reverse {boolean} - 是否反向播放的开始
 */
Tween.resetGroupToBeginning = function(node, groupId, reverse) {
    var func = function(tween) {
        tween.resetToBeginning(reverse);
    };
    Tween._funcGroup(node, groupId, func);
};

/**
 * 单次执行
 * @constant
 * @type {number}
 */
Tween.STYLE_ONCE = 0;
/**
 * 循环执行
 * @constant
 * @type {number}
 */
Tween.STYLE_LOOP = 1;
/**
 * 往返执行
 * @constant
 * @type {number}
 */
Tween.STYLE_PINGPONG = 2;
/**
 * 前进
 * @constant
 * @type {number}
 */
Tween.DIR_FORWARD = 0;
/**
 * 反转
 * @constant
 * @type {number}
 */
Tween.DIR_REVERSE = 1;

/**
 * 线性时间采样
 * @constant
 * @type {number}
 */
Tween.SAMPLE_LINEAR = 0;

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 淡入淡出动画组件
 * @class qc.TweenAlpha
 */
var TweenAlpha = defineBehaviour('qc.TweenAlpha', qc.Tween, function() {
    var self = this;

    /**
     * @property {number} from - 起始的透明度
     */
    self.from = 0;

    /**
     * @property {number} to - 最终的透明度
     */
    self.to = 1;

    // 默认情况下不可用
    self.enable = false;
},{
    from : qc.Serializer.NUMBER,
    to : qc.Serializer.NUMBER
});

// 菜单上的显示
TweenAlpha.__menu = 'Tween/TweenAlpha';

// 帧调度: 驱动位置
TweenAlpha.prototype.onUpdate = function(factor, isFinished) {
    var self = this;
    var _from = self.from, _to = self.to;
    self.gameObject.alpha = Phaser.Math.clamp(_from + factor * (_to - _from), 0, 1)
};

/**
 * 将开始状态设成当前状态
 */
TweenAlpha.prototype.setStartToCurrValue = function() {
    this.gameObject.alpha = this.from;
};

/**
 * 将结束状态设成当前状态
 */
TweenAlpha.prototype.setEndToCurrValue = function() {
    this.gameObject.alpha = this.to;
};

/**
 * 将当前状态设为开始状态
 */
TweenAlpha.prototype.setCurrToStartValue = function() {
    this.from = this.gameObject.alpha;
};

/**
 * 将当前状态设置为结束状态
 */
TweenAlpha.prototype.setCurrToEndValue = function() {
    this.to = this.gameObject.alpha;
};
/**
 * 开始透明化
 * @param node {qc.Node} - 需要改变的节点
 * @param duration {number} - 经历的时间
 * @param alpha {number} - 最终透明度
 * @returns {qc.TweenAlpha}
 */
TweenAlpha.begin = function(node, duration, alpha) {
    var tween = qc.Tween.begin('qc.TweenAlpha', node, duration);
    tween.from = node.alpha;
    tween.to  = alpha;
    if (duration <= 0) {
        tween.sample(1, true);
        tween.enable = false;
    }
    return tween;
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 颜色渐变动画
 * @class qc.TweenAlpha
 */
var TweenColor = defineBehaviour('qc.TweenColor', qc.Tween, function() {
    var self = this;

    /**
     * @property {qc.Color} from - 起始的颜色值
     */
    self.from = Color.black;

    /**
     * @property {qc.Color} to - 最终的颜色值
     */
    self.to = Color.white;

    // 默认情况下不可用
    self.enable = false;
},{
    from : qc.Serializer.COLOR,
    to : qc.Serializer.COLOR
});

// 菜单上的显示
TweenColor.__menu = 'Tween/TweenColor';

/**
 * 处理对应的变化逻辑
 * @param factor {number} - 形变的因子
 * @param isFinished {boolean} - 是否已经结束
 */
TweenColor.prototype.onUpdate = function(factor, isFinished) {
    var self = this;
    var _from = self.from.rgb, _to = self.to.rgb;
    var currColor = [
        Phaser.Math.clamp(Math.round(_from[0] + factor * (_to[0] - _from[0])), 0, 255),
        Phaser.Math.clamp(Math.round(_from[1] + factor * (_to[1] - _from[1])), 0, 255),
        Phaser.Math.clamp(Math.round(_from[2] + factor * (_to[2] - _from[2])), 0, 255),
        Phaser.Math.clamp(self.from.alpha + factor * (self.to.alpha - self.from.alpha), 0, 1)
    ];
    var color = new Color(currColor);
    self.gameObject.colorTint = color;
};

/**
 * 将开始状态设成当前状态
 */
TweenColor.prototype.setStartToCurrValue = function() {
    this.gameObject.colorTint = new Color(this.from.toString());
};

/**
 * 将结束状态设成当前状态
 */
TweenColor.prototype.setEndToCurrValue = function() {
    this.gameObject.colorTint = new Color(this.to.toString());
};

/**
 * 将当前状态设为开始状态
 */
TweenColor.prototype.setCurrToStartValue = function() {
    this.from = new Color(this.gameObject.colorTint.toString());
};

/**
 * 将当前状态设置为结束状态
 */
TweenColor.prototype.setCurrToEndValue = function() {
    this.to = new Color(this.gameObject.colorTint.toString());
};

/**
 * 开始变色
 * @param node {qc.Node} - 需要变色的节点
 * @param duration {number} - 变色的时间
 * @param color {qc.Color} - 最终颜色
 * @returns {qc.TweenColor}
 */
TweenColor.begin = function(node, duration, color) {
    var tween = qc.Tween.begin('qc.TweenColor', node, duration);
    tween.from = new Color(node.colorTint.toString());
    tween.to  = color;
    if (duration <= 0) {
        tween.sample(1, true);
        tween.enable = false;
    }
    return tween;
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 位置动画组件
 * @class qc.TweenPosition
 */
var TweenPosition = defineBehaviour('qc.TweenPosition', qc.Tween, function() {
    var self = this;

    /**
     * @property {qc.Point} from - 起始点
     */
    self.from = new qc.Point(0, 0);

    /**
     * @property {qc.Point} to - 终点
     */
    self.to = new qc.Point(0, 0);

    /**
     * @property {number} moveAxis - 当前改变位置的类型
     */
    self.moveAxis = TweenPosition.BOTH;

    // 默认情况下不可用
    self.enable = false;
},{
    from : qc.Serializer.POINT,
    to : qc.Serializer.POINT,
    moveAxis : qc.Serializer.NUMBER
});

// 菜单上的显示
TweenPosition.__menu = 'Tween/TweenPosition';

// 帧调度: 驱动位置
TweenPosition.prototype.onUpdate = function(factor, isFinished) {
    var self = this;
    var _from = self.from, _to = self.to;
    ((self.moveAxis & TweenPosition.ONLY_X) !== 0) && (self.gameObject.x = _from.x + factor * (_to.x - _from.x));
    ((self.moveAxis & TweenPosition.ONLY_Y) !== 0) && (self.gameObject.y = _from.y + factor * (_to.y - _from.y));
};

/**
 * 将开始位置设成当前位置
 */
TweenPosition.prototype.setStartToCurrValue = function() {
    ((this.moveAxis & TweenPosition.ONLY_X) !== 0) && (this.gameObject.x = this.from.x);
    ((this.moveAxis & TweenPosition.ONLY_Y) !== 0) && (this.gameObject.y = this.from.y);
};

/**
 * 将结束位置设成当前位置
 */
TweenPosition.prototype.setEndToCurrValue = function() {
    ((this.moveAxis & TweenPosition.ONLY_X) !== 0) && (this.gameObject.x = this.to.x);
    ((this.moveAxis & TweenPosition.ONLY_Y) !== 0) && (this.gameObject.y = this.to.y);
};

/**
 * 将当前位置设为开始位置
 */
TweenPosition.prototype.setCurrToStartValue = function() {
    this.from = new qc.Point(this.gameObject.x, this.gameObject.y);
};

/**
 * 将当前位置设置为结束位置
 */
TweenPosition.prototype.setCurrToEndValue = function() {
    this.to = new qc.Point(this.gameObject.x, this.gameObject.y);
};

/**
 * 开始缩放
 * @param node {qc.Node} - 需要移动的节点
 * @param duration {number} - 经历的时间
 * @param position {qc.Point} - 需要移动到的点
 * @returns {qc.TweenPosition}
 */
TweenPosition.begin = function(node, duration, position) {
    var tween = qc.Tween.begin('qc.TweenPosition', node, duration);
    tween.from = new qc.Point(node.x, node.y);
    tween.to  = position.clone();
    if (duration <= 0) {
        tween.sample(1, true);
        tween.enable = false;
    }
    return tween;
};

/**
 * x，y 轴位置都进行改变
 * @constant
 * @type {number}
 */
TweenPosition.BOTH = 255;
/**
 * 只改变 x 轴位置
 * @constant
 * @type {number}
 */
TweenPosition.ONLY_X = 1;
/**
 * 只改变 y 轴位置
 * @constant
 * @type {number}
 */
TweenPosition.ONLY_Y = 2;


/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 属性变化
 * @class qc.TweenProperty
 */
var TweenProperty = defineBehaviour('qc.TweenProperty', qc.Tween, function() {
    var self = this;

    /**
     * @property {number} from - 起始的数值
     */
    self.from = 0;

    /**
     * @property {number} to - 最终的数值
     */
    self.to = 1;

    /**
     * @property {string} property - 对象的属性
     */
    self.property = '';

    // 默认情况下不可用
    self.enable = false;
},{
    from : qc.Serializer.NUMBER,
    to : qc.Serializer.NUMBER,
    property: qc.Serializer.STRING
});

// 菜单上的显示
TweenProperty.__menu = 'Tween/TweenProperty';

Object.defineProperties(TweenProperty.prototype, {
    property: {
        get: function() { return this._property; },
        set: function(v) {
            if (v === this._property) return;

            // 记录对象和最后一级的属性
            var arr = v.split('.');
            var o = this.gameObject;
            for (var i = 0; i < arr.length - 1; i++) {
                o = o[arr[i]];
                if (!o) {
                    this.game.log.important('The property({0}) non-exist.', v);
                    return;
                }
            }
            this._ob = o;
            this._attrib = arr[arr.length - 1];
            this._property = v;
        }
    }
});

// 帧调度
TweenProperty.prototype.onUpdate = function(factor, isFinished) {
    var self = this;
    var _from = self.from, _to = self.to;

    self._ob[self._attrib] = _from + factor * (_to - _from);
};

/**
 * 将开始状态设成当前状态
 */
TweenProperty.prototype.setStartToCurrValue = function() {
    this._ob[this._attrib] = this.from;
};

/**
 * 将结束状态设成当前状态
 */
TweenProperty.prototype.setEndToCurrValue = function() {
    this._ob[this._attrib] = this.to;
};

/**
 * 将当前状态设为开始状态
 */
TweenProperty.prototype.setCurrToStartValue = function() {
    this.from = this._ob[this._attrib];
};

/**
 * 将当前状态设置为结束状态
 */
TweenProperty.prototype.setCurrToEndValue = function() {
    this.to = this._ob[this._attrib];
};
/**
 * 开始变化
 * @param node {qc.Node} - 需要改变的节点
 * @param duration {number} - 经历的时间
 * @param vallue {number} - 最终值
 * @returns {qc.TweenProperty}
 */
TweenProperty.begin = function(node, duration, value) {
    var tween = qc.Tween.begin('qc.TweenProperty', node, duration);
    tween.from = this._ob[this._attrib];
    tween.to  = value;
    if (duration <= 0) {
        tween.sample(1, true);
        tween.enable = false;
    }
    return tween;
};

/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 旋转动画组件
 * @class qc.TweenAlpha
 */
var TweenRotation = defineBehaviour('qc.TweenRotation', qc.Tween, function() {
    var self = this;

    /**
     * @property {number} from - 起始的弧度
     */
    self.from = 0;

    /**
     * @property {number} to - 最终的弧度
     */
    self.to = 1;

    // 默认情况下不可用
    self.enable = false;
},{
    from : qc.Serializer.NUMBER,
    to : qc.Serializer.NUMBER
});

// 菜单上的显示
TweenRotation.__menu = 'Tween/TweenRotation';

// 帧调度: 驱动动画
TweenRotation.prototype.onUpdate = function(factor, isFinished) {
    var self = this;
    var _from = self.from, _to = self.to;
    self.gameObject.rotation = _from + factor * (_to - _from);
};

/**
 * 将开始状态设成当前状态
 */
TweenRotation.prototype.setStartToCurrValue = function() {
    this.gameObject.rotation = this.from;
};

/**
 * 将结束状态设成当前状态
 */
TweenRotation.prototype.setEndToCurrValue = function() {
    this.gameObject.rotation = this.to;
};

/**
 * 将当前状态设为开始状态
 */
TweenRotation.prototype.setCurrToStartValue = function() {
    this.from = this.gameObject.rotation;
};

/**
 * 将当前状态设置为结束状态
 */
TweenRotation.prototype.setCurrToEndValue = function() {
    this.to = this.gameObject.rotation;
};

/**
 * 开始旋转
 * @param node {qc.Node} - 需要旋转的节点
 * @param duration {number} - 变色的时间
 * @param rotation {number} - 最终旋转角度
 * @returns {qc.TweenAlpha}
 */
TweenRotation.begin = function(node, duration, rotation) {
    var tween = qc.Tween.begin('qc.TweenRotation', node, duration);
    tween.from = node.rotation;
    tween.to  = rotation;
    if (duration <= 0) {
        tween.sample(1, true);
        tween.enable = false;
    }
    return tween;
};
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */;

/**
 * 缩放动画组件
 * @class qc.TweenAlpha
 */
var TweenScale = defineBehaviour('qc.TweenScale', qc.Tween, function() {
    var self = this;

    /**
     * @property {qc.Point} from - 起始的缩放信息
     */
    self.from = new qc.Point(0, 0);

    /**
     * @property {qc.Point} to - 最终的缩放信息
     */
    self.to = new qc.Point(1, 1);

    // 默认情况下不可用
    self.enable = false;
},{
    from : qc.Serializer.POINT,
    to : qc.Serializer.POINT
});

// 菜单上的显示
TweenScale.__menu = 'Tween/TweenScale';

// 帧调度: 驱动动画
TweenScale.prototype.onUpdate = function(factor, isFinished) {
    var self = this;
    var _from = self.from, _to = self.to;
    var scale = new qc.Point(
        _from.x + factor * (_to.x - _from.x),
        _from.y + factor * (_to.y - _from.y)
    );

    self.gameObject.scaleX = scale.x;
    self.gameObject.scaleY = scale.y;
};

/**
 * 将开始状态设成当前状态
 */
TweenScale.prototype.setStartToCurrValue = function() {
    this.gameObject.scaleX = this.from.x;
    this.gameObject.scaleY = this.from.y;
};

/**
 * 将结束状态设成当前状态
 */
TweenScale.prototype.setEndToCurrValue = function() {
    this.gameObject.scaleX = this.to.x;
    this.gameObject.scaleY = this.to.y;
};

/**
 * 将当前状态设为开始状态
 */
TweenScale.prototype.setCurrToStartValue = function() {
    this.from = new qc.Point(this.gameObject.scaleX, this.gameObject.scaleY);
};

/**
 * 将当前状态设置为结束状态
 */
TweenScale.prototype.setCurrToEndValue = function() {
    this.to = new qc.Point(this.gameObject.scaleX, this.gameObject.scaleY);
};

/**
 * 开始缩放
 * @param node {qc.Node} - 需要缩放的节点
 * @param duration {number} - 经历的时间
 * @param scale {qc.Point} - 最终缩放
 * @returns {qc.TweenScale}
 */
TweenScale.begin = function(node, duration, scale) {
    var tween = qc.Tween.begin('qc.TweenScale', node, duration);
    tween.from = new qc.Point(node.scaleX, node.scaleY);
    tween.to  = scale.clone();
    if (duration <= 0) {
        tween.sample(1, true);
        tween.enable = false;
    }
    return tween;
};

/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 垂直布局
 * 拓展TableLayout
 */
var VerticalLayout = defineBehaviour('qc.VerticalLayout', qc.TableLayout, function() {
    // 初始化参数
    this.style = qc.TableLayout.STYLE_RESIZE_ELEMENT;
    this.constraint = qc.TableLayout.CONSTRAINT_FIX_COLUMN_COUNT;
    this.contentSizeProvider = qc.TableLayout.USE_RECTTRANSFORM;
    this.stride = 1;
    this.startCorner = qc.TableLayout.CORNER_TOP_LEFT;
    this.startAxis = qc.TableLayout.AXIS_VERTICAL;
},{
    childForceExpandWidth : qc.Serializer.NUMBER,
    childForceExpandHeight : qc.Serializer.NUMBER
});

// 菜单上的显示
VerticalLayout.__menu = 'UI/Layout/VerticalLayout';

Object.defineProperties(VerticalLayout.prototype, {
    /**
     * @property {number} spacing - 布局间距
     */
    spacing : {
        get : function() { return this.spacingY; },
        set : function(value) { this.spacingY = value; }
    },

    /**
     * @property {number} childForceExpandWidth - 是否强制拉伸宽度
     */
    childForceExpandWidth : {
        get : function() { return !!this._childForceExpandWidth; },
        set : function(value) {
            if (this._childForceExpandWidth === value) {
                return;
            }
            this._childForceExpandWidth = value;
            this.rebuildTable();
        }
    },

    /**
     * @property {number} childForceExpandWidth - 是否强制拉伸高度
     */
    childForceExpandHeight : {
        get : function() { return !!this._childForceExpandHeight; },
        set : function(value) {
            if (this._childForceExpandHeight === value) {
                return;
            }
            this._childForceExpandHeight = value;
            this.rebuildTable();
        }
    }
});

/**
 * 获取在某一轴线上的默认布局系数
 * @override
 * @private
 */
VerticalLayout.prototype._getDefaultLayout = function(axis){
    if (axis === 'y') {
        return {min : 0,
            preferred : 0,
            flexible : this.childForceExpandHeight ? 1 : 0,
            extra : 0};
    }
    else {
        return {min : 0,
            preferred : this.childForceExpandWidth ? this.gameObject.rect.width : -1,
            flexible : 0,
            extra : 0};
    }

};

/**
 * 获取在某一节点的布局系数
 * @param node {qc.Node} - 节点
 * @returns {*|LayoutElement}
 * @override
 */
VerticalLayout.prototype.getCellLayout = function(node) {
    var layout = qc.LayoutElement.getLayoutElement(node);
    this.childForceExpandWidth && (layout.flexibleWidth = 1);
    this.childForceExpandHeight && (layout.flexibleHeight = 1);
    return layout;
};
/**
 * @author luohj
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 时间
 *
 * @class qc.Time
 * @param {qc.Game} game - A reference to the currently running game.
 * @constructor
 * @internal
 */
var Time = qc.Time = function(game) {
    this.phaser = game.phaser.time;
    this.phaser._qc = this;
}
Time.prototype.constructor = Time;

Object.defineProperties(Time.prototype, {

    'game' : {
        get : function() { return this.phaser.game._qc; }
    },

    /**
     *  现在的时间戳 (1970-01-01午夜到现在的时间间隔，用毫秒表述)
     *  @property {number} now
     *  @return {number}
     */
    'now' : { get : function() { return Date.now(); } },

    /**
     * @property fixedTime {number} - 这是以毫秒秒计自游戏开始的时间
     */
    'fixedTime' : { get : function() { return this.phaser.totalElapsedSeconds() * 1000 ; } },

    /**
     * @property scaleTime {number} -  自游戏开始后经历的时间，受timeScale影响
     */
    'scaledTime' : {
        get : function() {
            return this.phaser._totalEscapeTime || 0;
        }
    },
    /**
     * 传递时间的缩放。这可以用于减慢运动效果。
     * - 1.0 = normal speed
     * - 2.0 = half speed
     * @property {number} timeScale
     */
    'timeScale' : {
        get : function()  { return this.phaser.slowMotion; },
        set : function(v) {
            if (v <= 0) v = 0.000001;
            this.phaser.slowMotion = v;
        }
    },

    /**
     * @property {number} frameRate - 游戏运行的帧率
     */
    'frameRate' : {
        get : function() { return this.phaser.desiredFps; },
        set : function(v) { this.phaser.desiredFps = v; }
    },

    /**
     * @property {number} deltaTime - 最后一帧到当前帧的时间间隔（单位：毫秒）
     */
    'deltaTime' : { get : function() { return this.phaser.frameDeltaTime; } }
});

Time.prototype.applyFrameRate = function(v) {
    var frameRate = this.game.device.desktop ? v.desktop : v.mobile;
    this.frameRate = frameRate;
};



/**
 * @author luohj
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 链表的实现（暂时只为Timer服务）
 */
var LinkList = qc.LinkList = function(capacity) {
    /**
     * 链表的长度
     */
    this.length = capacity;

    /**
     * 头
     */
    this.head = null;

    /**
     * 尾部
     */
    this.tail = null;

    while (capacity--) {
        this.push({});
    }
};
LinkList.prototype = {};
LinkList.prototype.constructor = LinkList;

/**
 * 添加一个节点到队列尾部
 */
LinkList.prototype.push = function(node) {
    node.pre = null;
    node.next = null;
    if (!this.head) {
        this.head = node;
        this.tail = this.head;
    }
    else {
        this.tail.next = node;
        node.pre = this.tail;
        this.tail = this.tail.next;
    }
    this.length++;
};

/**
 * 插入到一个节点之前
 */
LinkList.prototype.insertBefore = function(node, newNode) {
    if (!node) {
        this.push(newNode);
        return;
    }

    var old = node.pre;
    node.pre = newNode;
    newNode.next = node;
    newNode.pre = old;
    if (old)
        old.next = newNode;
    if (!newNode.pre) this.head = newNode;
};

LinkList.prototype.remove = function(node) {
    var pre = node.pre,
        next = node.next;
    if (pre) pre.next = next;
    if (next) next.pre = pre;

    if (node === this.head) this.head = next;
    if (node === this.tail) this.tail = pre;
    node.pre = null;
    node.next = null;
    this.length--;
};

/**
 * 定时器
 *
 * @class qc.Timer
 * @param {qc.Game} game - A reference to the currently running game.
 * @constructor
 * @internal
 */
var Timer = qc.Timer = function(game) {
    var self = this;
    self.game = game;

    /**
     * @property {boolean} paused - The paused state of the Timer. You can pause the timer by calling Timer.pause() and Timer.resume() or by the game pausing.
     * @readonly
     * @default
     */
    self.paused = false;

    /**
     * @property {Phaser.TimerEvent[]} events - An array holding all of this timers Phaser.TimerEvent objects. Use the methods add to populate it.
     */
    self.events = new qc.LinkList(0);

    /**
     * @property {Phaser.TimerEvent[]} events - An array holding all of this timers Phaser.TimerEvent objects. Use the methods loop to populate it.
     */
    self.loopEvents = new qc.LinkList(0);

    // 下个 timer 的起始时间
    this._nextTimerBegin = 0;
    this._nextLoopTimerBegin = 0;
};
Timer.prototype.constructor = Timer;

/**
 * Creates a new TimerEvent on this Timer
 */
Timer.prototype.create = function (delay, loop, callback, callbackContext, args) {
    delay = Math.round(delay);
    var event = new Phaser.TimerEvent(this, delay, delay, 0, loop, callback, callbackContext, args);

    // 如果是循环的定时器，扔到循环队列中
    this._pushEvent(loop ? this.loopEvents : this.events, event);
    return event;
};

/**
 * 添加一个定时器，执行一次后即刻销毁
 *
 * @method qc.Timer#add
 * @param {number} delay - 延迟执行的时间，单位：毫秒
 * @param {function} callback - 定时器到了后的处理
 * @param {object} callbackContext - 回调上下文
 * @param {...*} arguments - 定时器的参数列表，回调时会原样带回
 * @return {qc.TimerEvent} 定时器对象
 */
Timer.prototype.add = function(delay, callback, callbackContext) {
    // 最小时间保护
    var delay = delay > 0 ? delay : 1;

    // _nextTimerBegin 是用作保护在当前帧的定时器中设定的新的 timer 必须要在下次调度中执行，而不能在当帧
    delay += this._nextTimerBegin;

    return this.create(delay, false, callback, callbackContext, Array.prototype.splice.call(arguments, 3));
}

/**
 * 添加一个定时器，循环不休止的调用
 *
 * @method qc.Timer#add
 * @param {number} delay - 延迟执行的时间，单位：毫秒
 * @param {function} callback - 定时器到了后的处理
 * @param {object} callbackContext - 回调上下文
 * @param {...*} arguments - 定时器的参数列表，回调时会原样带回
 * @return {qc.TimerEvent} 定时器对象
 */
Timer.prototype.loop = function(delay, callback, callbackContext) {
    // 最小时间保护
    var delay = delay > 0 ? delay : 1;

    // _nextLoopTimerBegin 是用作保护在当前帧的定时器中设定的新的 timer 必须要在下次调度中执行，而不能在当帧
    delay += this._nextLoopTimerBegin;

    return this.create(delay, true, callback, callbackContext, Array.prototype.splice.call(arguments, 3));
};

/**
 * 删除一个定时器
 *
 * @param {qc.TimerEvent} id - 定时器对象
 * @method qc.Timer#remove
 */
Timer.prototype.remove = function(id) {
    // 标记下需要删除了
    id.__delete = true;
};

/**
 * 定时器帧调度
 */
Timer.prototype.update = function(time) {
    if (this.paused) return;

    // 初始化 _now 值，不难会造成 elapsed 为 NaN，导致 while 循环可能出现死循环的问题
    // 具体见任务`timer死循环的问题处理`
    this._now = this._now ? this._now : time;
    var elapsedRaw = (time - this._now) / this.game.time.timeScale;
    this._now = time;

    // 处理一次性的定时器
    var curr = this.events.head;
    var elapsed = elapsedRaw;
    var loopCount = 0;
    while (curr) {
        curr.delta -= elapsed;
        if (curr.delta > 0) break;

        // 避免死循环，原因也可见任务`timer死循环的问题处理`
        loopCount++;
        if (loopCount > 2000)
        {
            throw new Error('timer loop forever!');
            break;
        }

        // 这个定时器事件到了，需要处理之
        this._nextTimerBegin = elapsed = -curr.delta;
        this.events.remove(curr);
        if (!curr.__delete)
            curr.callback.apply(curr.callbackContext, curr.args);
        curr = this.events.head;
    }
    this._nextTimerBegin = 0;

    // 处理循环的定时器
    var list = [];
    curr = this.loopEvents.head;
    elapsed = elapsedRaw;
    while (curr) {
        curr.delta -= elapsed;
        if (curr.delta > 0) break;

        // 这个定时器事件到了，需要处理之
        this._nextLoopTimerBegin = elapsed = -curr.delta;
        this.loopEvents.remove(curr);
        if (!curr.__delete) {
            list.push(curr);
            curr.callback.apply(curr.callbackContext, curr.args);
        }
        curr = this.loopEvents.head;
    }
    this._nextLoopTimerBegin = 0;

    // 重新将这些定时器加进来
    for (var i = 0; i < list.length; i++) {
        this._pushEvent(this.loopEvents, list[i]);
    }
};

/**
 * 将定时器扔进队列中
 * @private
 */
Timer.prototype._pushEvent = function(list, event) {
    var curr = list.head;

    if (!curr) {
        // 第一个
        event.delta = event.delay;
        list.push(event);
        return;
    }

    // 找到合适的位置插入
    var tick = event.delay;
    while (curr) {
        if (tick >= curr.delta) {
            tick -= curr.delta;
            curr = curr.next;
            continue;
        }

        // 找到了，插入
        curr.delta -= tick;
        event.delta = tick;

        list.insertBefore(curr, event);
        return event;
    }

    // 到达末尾了，插入到最后面
    event.delta = tick;
    list.push(event);
    return event;
};

/**
 * @author luohj
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 定时器对象
 *
 * @class qc.TimerEvent
 */
qc.TimerEvent = Phaser.TimerEvent;

/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * Filter 对象基类
 * 注意：
 * 着色器中，只能额外添加7个贴图
 * 配置贴图名为 bbbbb, 对应的坐标为aBbbbbCoord
 */
var Filter = qc.Filter = function(game) {
    Phaser.Filter.call(this, game);
    this._textureCount = 1;
    this.extraAttribute = [];
};
Filter.prototype = Object.create(Phaser.Filter.prototype);
Filter.prototype.constructor = Filter;

Object.defineProperties(Filter.prototype, {
    /**
     * @property {number} textureCount - 当前着色器使用的贴图数量
     */
    textureCount : {
        get : function() { return this._textureCount; }
    }
});

/**
 * 保存所有的自定义着色器
 * @type {{}}
 */
Filter.filters = {};

/**
 * 着色器可以用的绑定类型
 */
Filter.F1 = '1f';
Filter.F1V = '1fv';
Filter.I1 = '1i';
Filter.F2 = '2f';
Filter.F2V = '2fv';
Filter.I2 =  '2i';
Filter.I2V = '2iv';
Filter.F3 = '3f';
Filter.F3V = '3fv';
Filter.I3 = '3i';
Filter.I3V = '3iv';
Filter.F4 = '4f';
Filter.F4V = '4fv';
Filter.I4 = '4i';
Filter.I4V = '4iv';
Filter.MAT2 = 'mat2';
Filter.MAT3 = 'mat3';
Filter.MAT4 = 'mat4';
Filter.SAMPLER2D = 'sampler2D';

/**
 * 一个着色器中最大的贴图数量
 * @constant
 * @type {number}
 */
Filter.MAX_SAMPLE_COUNT = 8;

Filter.mixinUniform = function(ob, extra) {
    if (!extra)
        return;
    for (var name in extra) {
        var type = extra[name];
        var isSampler = type === Filter.SAMPLER2D;
        if (isSampler && ob.textureCount >= Filter.MAX_SAMPLE_COUNT) {
            continue;
        }
        var valueFunc = function() {
            var key = name;
            if (isSampler) {
                ob._textureCount++;
                return function() {
                    return ob[key] && ob[key].filterTexture;
                };
            }
            else {
                return function() {
                    return ob[key];
                };
            }

        };
        ob.uniforms[name] = new UniformHelp(type, valueFunc());
        if (isSampler) {
            var coordName = 'a' + name.substring(0,1).toUpperCase() + name.substring(1) + 'Coord';
            ob.extraAttribute.push({name : coordName, texture: name});
        }
    }
};

/**
 * 将一个 uniform 字段序列化
 * @param uniformType
 * @param value
 * @param context
 * @returns {*[]}
 */
Filter.uniformToJson = function(uniformType, value, context) {
    switch (uniformType) {
        case Filter.F1:
        case Filter.I1:
        case Filter.I2V:
        case Filter.F2V:
        case Filter.I3V:
        case Filter.F3V:
        case Filter.I4V:
        case Filter.F4V:
        case Filter.MAT2:
        case Filter.MAT3:
        case Filter.MAT4:
            return [uniformType, value];
        case Filter.I2:
        case Filter.F2:
            return [uniformType, [value.x, value.y]];
        case Filter.I3:
        case Filter.F3:
            return [uniformType, [value.x, value.y, value.z]];
        case Filter.I4:
        case Filter.F4:
            return [uniformType, [value.x, value.y, value.z, value.w]];
        case Filter.SAMPLER2D:
            if (!(value instanceof FilterTexture)) return [uniformType, []];
            var atlas = value.texture;
            if (atlas) {
                // 记录资源依赖
                context.dependences.push({
                    key : atlas.key,
                    uuid : atlas.uuid
                });
                return [uniformType, [atlas.key, atlas.url, atlas.uuid, value.frame]];    
            }
            else {
                return [uniformType, []];
            }
            
    }
    return [uniformType, []];
};

/**
 * 将一个 uniform 字段反序列化为值
 * @param ob
 * @param key
 * @param uniformType
 * @param value
 * @returns {*}
 */
Filter.uniformFromJson = function(game, ob, key, uniformType, value) {
    if (!value) {
        return;
    }
    switch (uniformType) {
        case Filter.F1:
        case Filter.I1:
        case Filter.I2V:
        case Filter.F2V:
        case Filter.I3V:
        case Filter.F3V:
        case Filter.I4V:
        case Filter.F4V:
        case Filter.MAT2:
        case Filter.MAT3:
        case Filter.MAT4:
            ob[key] = value;
            return;
        case Filter.I2:
        case Filter.F2:
            if (ob[key]) {
                ob[key].x = value[0];
                ob[key].y = value[1];
            }
            else {
                ob[key] = { x: value[0], y : value[1] };
            }
            return;
        case Filter.I3:
        case Filter.F3:
            if (ob[key]) {
                ob[key].x = value[0];
                ob[key].y = value[1];
                ob[key].z = value[2];
            }
            else {
                ob[key] = { x: value[0], y : value[1], z : value[2] };
            }
            return;
        case Filter.I4:
        case Filter.F4:
            if (ob[key]) {
                ob[key].x = value[0];
                ob[key].y = value[1];
                ob[key].z = value[2];
                ob[key].w = value[3];
            }
            else {
                ob[key] = { x: value[0], y : value[1], z : value[2], w : value[3] };
            }
            return;
        case Filter.SAMPLER2D:
            if (!value) return;
            var texture = new FilterTexture(game);
            if (value.length > 0) {
                var atlas = game.assets.find(value[0], value[1]);
                if (!atlas)
                    atlas = game.assets.findByUUID(value[2]);
                if (!atlas) {
                    game.log.error('Texture({0}) not loaded, deserialize fail.', value[1]);
                    return;
                }
                if (!(atlas instanceof qc.Atlas))
                    return;

                texture.texture = atlas;
                texture.frame = value[3];
            }
            ob[key] = texture;
            return;
    }
};

/**
 * 序列化一个着色器对象
 * @param ob
 * @param json
 * @param context
 * @param key
 * @param value
 * @private
 */
Serializer.prototype._saveFilter = function(ob, json, context, key, value) {
    if (value)
        json[key] = this._saveFilterItem(value, context);
};

/**
 * 反序列化一个着色器对象
 * @param ob
 * @param json
 * @param key
 * @param value
 * @private
 */
Serializer.prototype._restoreFilter = function(ob, json, key, value) {
    ob[key] = this._restoreFilterItem(value);
};

/**
 * 序列化一个着色器对象
 * @param value
 * @param context
 * @private
 */
Serializer.prototype._saveFilterItem = function(value, context) {
    var extra = value.getExtraUniforms();
    var json = {};
    for (var name in extra) {
        var type = extra[name];
        json[name] = Filter.uniformToJson(type, value[name], context);
    }
    return [Serializer.FILTER, value.class, json];
};

/**
 * 反序列化着色器
 * @param value
 * @returns {cls}
 * @private
 */
Serializer.prototype._restoreFilterItem = function(value) {
    var cls = qc.Util.findClass(value[1]);
    if (!cls) {
        console.warn('着色器脚本：{0}未找到，无法反序列化。', value[1]);
        return;
    }
    var uniforms = value[2];
    var filter = new cls();
    var extra = filter.getExtraUniforms();
    for (var name in extra) {
        var type = extra[name];
        if (!uniforms[name] || type !== uniforms[name][0]) {
            continue;
        }
        Filter.uniformFromJson(this.game, filter, name, type, uniforms[name][1]);
    }
    return filter;
};

/**
 * 定义一个着色器
 * @type {Function}
 */
var defineFilter = qc.defineFilter = function(clazz, parent, init, extraUniforms) {
// 先将clazz分解下，并创建包
    var arr = clazz.split('.');
    var curr = window;
    for (var i = 0; i < arr.length - 1; i++) {
        if (!curr[arr[i]]) curr[arr[i]] = {};
        curr = curr[arr[i]];
    }
    var name = arr[arr.length - 1];

    parent = parent || qc.Filter;
    if (typeof extraUniforms === 'function')
    // 指定了函数，则调用下取返回值
        extraUniforms = extraUniforms.call(this);
    extraUniforms = extraUniforms || {};
    curr[name] = function(game) {
        // 记录父亲和类名
        parent.call(this, game);
        this.class = clazz;
        this.super = parent.prototype;
        var uniforms = this.getExtraUniforms();
        Filter.mixinUniform(this, uniforms);

        // 调用初始化函数
        if (typeof init === 'function')
            init.call(this, game);
    }
    curr[name].prototype = Object.create(parent.prototype);
    curr[name].prototype.constructor = curr[name];

    // 设置需要同步到显卡的字段
    curr[name].prototype.getExtraUniforms = function() {
        var uniforms = parent.prototype.getExtraUniforms ? parent.prototype.getExtraUniforms.call(this) : {};
        // 合并下
        return mixin(uniforms, extraUniforms);
    };

    curr[name].prototype.clazz = clazz;

    Filter.filters[clazz] = curr[name];
    return curr[name];
};
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 着色器使用的贴图
 */
var FilterTexture = qc.Filter.FilterTexture = function(game) {
    this._image = new qc.UIImage(game, null, false);
    this._image.parent.removeChild(this._image);
};

FilterTexture.prototype = {};
FilterTexture.prototype.constructor = FilterTexture;

Object.defineProperties(FilterTexture.prototype, {
    /**
     *  @property {qc.Atlas} texture - 获取or设置当前的图片
     */
    texture : {
        get : function() {
            return this._image.texture;
        },
        set : function(v) {
            this._image.texture = v;
        }
    },

    /**
     *  @property {int|string} frame - 获取or设置当前的图片帧，一般是图集才会用到该属性（可以为数字或别名）
     */
    frame : {
        get: function () {
            return this._image.frame;
        },

        set: function (value) {
            this._image.frame = value;
        }
    },

    /**
     * @property {PIXI.Texture} filterTexture - 用于着色器使用的贴图
     */
    filterTexture : {
        get : function() {
            return this._image.phaser.texture;
        }
    }
});
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 同步数据的辅助类
 * @param type {string} - Filter Uniform Type
 * @param value {*|func} - 需要同步的值或者函数
 */
var UniformHelp = qc.Filter.UniformHelp = function(type, value) {
    /**
     * @property {string} _type - 同步类型
     * @private
     */
    this._type = type;
    /**
     * @property {*} _value - 同步的值
     * @private
     */
    this._value = value;
    /**
     * @property {boolean} _valueIsFunc - 值是否为函数
     * @private
     */
    this._valueIsFunc = (typeof this._value === 'function');
};
UniformHelp.prototype = {};
UniformHelp.prototype.constructor = UniformHelp;

Object.defineProperties(UniformHelp.prototype, {
    /**
     * @property {string} type - 同步的类型
     * @readonly
     */
    type : {
        get : function() { return this._type; }
    },
    /**
     * @property {*} value - 同步的值
     * @readonly
     */
    value : {
        get : function() {
            if (this._valueIsFunc) {
                return this._value();
            }
            else {
                return this._value;
            }
        }
    }
});


/**
 * Created by qcplay on 7/9/15.
 */
var BlendTexture = defineFilter('qc.Filter.AlphaMask', qc.Filter, function(game) {
    this.otherTexture = null;
    this.vertexSrc = [
        'attribute vec2 aVertexPosition;',
        'attribute vec2 aTextureCoord;',
        'attribute vec2 aOtherTextureCoord;',
        'attribute vec4 aColor;',

        'uniform vec2 projectionVector;',
        'uniform vec2 offsetVector;',

        'varying vec2 vTextureCoord;',
        'varying vec2 vOtherTextureCoord;',
        'varying vec4 vColor;',

        'const vec2 center = vec2(-1.0, 1.0);',

        'void main(void) {',
        '   gl_Position = vec4( ((aVertexPosition + offsetVector) / projectionVector) + center , 0.0, 1.0);',
        '   vTextureCoord = aTextureCoord;',
        '   vOtherTextureCoord = aOtherTextureCoord;',
        '   vColor = vec4(aColor.rgb * aColor.a, aColor.a);',
        '}'
    ];
    this.fragmentSrc = [

        'precision mediump float;',
        'varying vec2 vTextureCoord;',
        'varying vec2 vOtherTextureCoord;',
        'varying vec4 vColor;',

        'uniform sampler2D uSampler;',
        'uniform sampler2D otherTexture;',

        'void main(void) {',
        '   vec4 original =  texture2D(uSampler, vTextureCoord);',
        '   vec4 add =  texture2D(otherTexture, vOtherTextureCoord);',
        '   gl_FragColor = add.a * original;',
        '}'

    ];
},{
    otherTexture : qc.Filter.SAMPLER2D
});
/**
 * Created by qcplay on 7/9/15.
 */
var BlendTexture = defineFilter('qc.Filter.BlendTexture', qc.Filter, function(game) {
    this.mixing = 0.5;
    this.otherTexture = null;
    this.vertexSrc = [
        'attribute vec2 aVertexPosition;',
        'attribute vec2 aTextureCoord;',
        'attribute vec2 aOtherTextureCoord;',
        'attribute vec4 aColor;',

        'uniform vec2 projectionVector;',
        'uniform vec2 offsetVector;',

        'varying vec2 vTextureCoord;',
        'varying vec2 vOtherTextureCoord;',
        'varying vec4 vColor;',

        'const vec2 center = vec2(-1.0, 1.0);',

        'void main(void) {',
        '   gl_Position = vec4( ((aVertexPosition + offsetVector) / projectionVector) + center , 0.0, 1.0);',
        '   vTextureCoord = aTextureCoord;',
        '   vOtherTextureCoord = aOtherTextureCoord;',
        '   vColor = vec4(aColor.rgb * aColor.a, aColor.a);',
        '}'
    ];
    this.fragmentSrc = [

        'precision mediump float;',
        'varying vec2 vTextureCoord;',
        'varying vec2 vOtherTextureCoord;',
        'varying vec4 vColor;',

        'uniform sampler2D uSampler;',
        'uniform sampler2D otherTexture;',
        'uniform float mixing;',

        'void main(void) {',
        '   vec4 original =  texture2D(uSampler, vTextureCoord);',
        '   vec4 add =  texture2D(otherTexture, vOtherTextureCoord);',
        '   gl_FragColor = mixing * add + (1.0 - mixing) * original;',
        '}'

    ];
},{
    otherTexture : qc.Filter.SAMPLER2D,
    mixing : qc.Filter.F1
});
/**
* A horizontal blur filter by Mat Groves http://matgroves.com/ @Doormat23
*/
var BlurX = defineFilter('qc.Filter.BlurX', qc.Filter, function(game) {
    this.fragmentSrc = [

      "precision mediump float;",
      "varying vec2 vTextureCoord;",
      "varying vec4 vColor;",
      "uniform float blur;",
      "uniform sampler2D uSampler;",
      "uniform vec2 pixelSize;",

        "void main(void) {",

          "vec4 sum = vec4(0.0);",

          "sum += texture2D(uSampler, vec2(vTextureCoord.x - 4.0*blur*pixelSize.x, vTextureCoord.y)) * 0.05;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x - 3.0*blur*pixelSize.x, vTextureCoord.y)) * 0.09;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x - 2.0*blur*pixelSize.x, vTextureCoord.y)) * 0.12;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x - blur*pixelSize.x, vTextureCoord.y)) * 0.15;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * 0.16;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x + blur*pixelSize.x, vTextureCoord.y)) * 0.15;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x + 2.0*blur*pixelSize.x, vTextureCoord.y)) * 0.12;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x + 3.0*blur*pixelSize.x, vTextureCoord.y)) * 0.09;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x + 4.0*blur*pixelSize.x, vTextureCoord.y)) * 0.05;",
          "gl_FragColor = sum;",

        "}"
    ];

    this.blur = 1;
},{
    blur : qc.Filter.F1
});

Object.defineProperty(BlurX.prototype, 'blur', {

    get: function() {
        return this._blur;
    },

    set: function(value) {
        this.dirty = true;
        this._blur = value;
    }

});
/**
* A vertical blur filter by Mat Groves http://matgroves.com/ @Doormat23
*/
var BlurY = defineFilter('qc.Filter.BlurY', qc.Filter, function(game) {
    this.fragmentSrc = [

      "precision mediump float;",
      "varying vec2 vTextureCoord;",
      "varying vec4 vColor;",
      "uniform float blur;",
      "uniform sampler2D uSampler;",
      "uniform vec2 pixelSize;",

        "void main(void) {",

          "vec4 sum = vec4(0.0);",

          "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - 4.0*blur*pixelSize.y)) * 0.05;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - 3.0*blur*pixelSize.y)) * 0.09;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - 2.0*blur*pixelSize.y)) * 0.12;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y - blur*pixelSize.y)) * 0.15;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * 0.16;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + blur*pixelSize.y)) * 0.15;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + 2.0*blur*pixelSize.y)) * 0.12;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + 3.0*blur*pixelSize.y)) * 0.09;",
          "sum += texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y + 4.0*blur*pixelSize.y)) * 0.05;",

          "gl_FragColor = sum;",

        "}"

    ];

    this.blur = 1;
}, {
    blur : qc.Filter.F1
});

Object.defineProperty(BlurY.prototype, 'blur', {

    get: function() {
        return this._blur;
    },

    set: function(value) {
        this.dirty = true;
        this._blur = value;
    }

});

/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */
/**
 * 着色器使用的绘制贴图
 */

var GraphicsTexture = qc.Filter.GraphicsTexture = function(game, width, height, render, resolution) {
    var self = this;

    /**
     * 游戏对象
     * @type {qc.Game}
     */
    self.game = game;

    /**
     * 使用的贴图
     * @type {qc.RenderTexture}
     */
    self._texture = new qc.RenderTexture(width || 100, height || 100, render || self.game.phaser.render, null, resolution || self.game.resolution);

    /**
     * Graphics在Texture中呈现时的偏移
     * @type {qc.Point}
     */
    self.offset = new qc.Point(0, 0);

    /**
     * 绘制对象
     * @type {PIXI.Graphics}
     */
    self._graphics = self.game.phaser.add.graphics(0, 0);
    if (self._graphics.parent) {
        self._graphics.parent.removeChild(self._graphics);
    }
};

GraphicsTexture.prototype = {};
GraphicsTexture.prototype.constructor = GraphicsTexture;

Object.defineProperties(GraphicsTexture.prototype, {
    /**
     *  @property {PIXI.Graphics} graphics - 用来绘制图元的绘制对象
     *  @readonly
     */
    graphics : {
        get : function() {
            return this._graphics;
        }
    },

    /**
     * @property {PIXI.Texture} filterTexture - 用于着色器使用的贴图
     */
    filterTexture : {
        get : function() {
            return this._texture;
        }
    }
});

/**
 * 更新贴图
 */
GraphicsTexture.prototype.updateTexture = function() {
    var self = this,
        texture = self._texture,
        g = self._graphics;
    if (g.dirty) {
        texture.directRenderWebGL(g, self.offset, true);    
    }
};

/**
 * 修改贴图的大小
 * @param  {number} width  - 贴图的宽
 * @param  {number} height - 贴图的高
 */
GraphicsTexture.prototype.resize = function(width, height) {
    width = width <= 1 ? 1 : width;
    height = height <= 1 ? 1 : height;
    this._texture.resize(width, height, true);
};
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 灰度化
 */
var Gray = defineFilter('qc.Filter.Gray', qc.Filter, function(game) {
    this.fragmentSrc = [

        "precision mediump float;",
        "varying vec2 vTextureCoord;",
        "varying vec4 vColor;",
        "uniform sampler2D uSampler;",

        "void main(void) {",
        '   vec4 original =  texture2D(uSampler, vTextureCoord);',
        '   float gray = original.r * 0.3 + original.g * 0.59 + original.b * 0.11;',
        '   gl_FragColor =  original;',
        '   gl_FragColor.r = gray;',
        '   gl_FragColor.g = gray;',
        '   gl_FragColor.b = gray;',
        "}"
    ];

}, {

});
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 灰度化
 */
var Highlight = defineFilter('qc.Filter.Highlight', qc.Filter, function(game) {
    this.vertexSrc = [
        'attribute vec2 aVertexPosition;',
        'attribute vec2 aTextureCoord;',
        'attribute vec4 aColor;',

        'uniform vec2 projectionVector;',
        'uniform vec2 offsetVector;',
        "uniform float light;",
        "uniform vec3 lightColor;",

        'varying vec2 vTextureCoord;',
        'varying vec4 vColor;',
        'varying vec4 vLight;',

        'const vec2 center = vec2(-1.0, 1.0);',

        'void main(void) {',
        '   gl_Position = vec4( ((aVertexPosition + offsetVector) / projectionVector) + center , 0.0, 1.0);',
        '   vTextureCoord = aTextureCoord;',
        '   vColor = vec4(aColor.rgb * aColor.a, aColor.a);',
        '   vLight = light * vec4(lightColor, 0.0);',
        '}'
    ];
    this.fragmentSrc = [
        "precision mediump float;",
        "varying vec2 vTextureCoord;",
        "varying vec4 vColor;",
        'varying vec4 vLight;',

        "uniform sampler2D uSampler;",


        "void main(void) {",
        '   vec4 original =  texture2D(uSampler, vTextureCoord);',
        '   gl_FragColor = original + original.a * vLight;',
        "}"
    ];
    this.light = 0.5;
    this.lightColor = [1, 1, 1];
}, {
    light : qc.Filter.F1,
    lightColor : qc.Filter.F3V
});
/**
 * @author chenqx
 * copyright 2015 Qcplay All Rights Reserved.
 */

/**
 * 为 shader处理 保持原图效果
 */
var KeepSource = defineFilter('qc.Filter.KeepSource', qc.Filter, function(game) {
    this.fragmentSrc = [

        "precision mediump float;",
        "varying vec2 vTextureCoord;",
        "varying vec4 vColor;",
        'varying vec2 vMaskCoord;',
        'varying vec4 vMaskLimit;',

        "uniform sampler2D uSampler;",
        "uniform sampler2D uSourceSampler;",

        "void main(void) {",
        '   vec4 original =  texture2D(uSampler, vTextureCoord);',
        '   vec4 add =  texture2D(uSourceSampler, vTextureCoord);',
        '   gl_FragColor =  add.a * add + (1.0 - add.a) * original;',
        "}"

    ];

}, {

});
/**
 * @author weism
 * copyright 2015 Qcplay All Rights Reserved.
 */

}).call(this, this, Object);
