Represents a mainline key.
        void ReadMainlineObjectRef(XmlElement element, SpriterMainlineKey key)
        {
            var obj = new SpriterMainlineObjectRef();
            key.objects.Add(obj);

            foreach(XmlAttribute attribute in element.Attributes)
            {
                // id
                if (attribute.Name.Equals("id"))
                    obj.ID = int.Parse(attribute.Value);

                // parent
                else if (attribute.Name.Equals("parent"))
                    obj.parent = int.Parse(attribute.Value);

                // timeline
                else if (attribute.Name.Equals("timeline"))
                    obj.timeline = int.Parse(attribute.Value);

                // key
                else if (attribute.Name.Equals("key"))
                    obj.key = int.Parse(attribute.Value);

                // z_index
                else if (attribute.Name.Equals("z_index"))
                    obj.zIndex = int.Parse(attribute.Value);
            }
        }
        void ReadMainline(XmlElement element, SpriterAnimation animation)
        {
            foreach(XmlElement child in element)
            {
                // key
                if (child.Name.Equals("key"))
                {
                    SpriterMainlineKey key = new SpriterMainlineKey();
                    animation.mainline.keys.Add(key);

                    foreach(XmlAttribute attribute in child.Attributes)
                    {
                        // id
                        if (attribute.Name.Equals("id"))
                            key.ID = int.Parse(attribute.Value);

                        // time
                        else if (attribute.Name.Equals("time"))
                            key.time = int.Parse(attribute.Value);
                    }

                    foreach(XmlElement child2 in child)
                    {
                        // meta_data
                        if (child2.Name.Equals("meta_data"))
                            ReadMetaData(child2, key.metaData);

                        // hierarchy
                        else if (child2.Name.Equals("hierarchy"))
                            ReadHierarchy(child2, key.hierarchy);

                        // object
                        else if (child2.Name.Equals("object"))
                            ReadMainlineObject(child2, key);

                        // object_ref
                        else if (child2.Name.Equals("object_ref"))
                            ReadMainlineObjectRef(child2, key);

                        // Spriter doesn't always output files to the spec;
                        // check for bones here too
                        // bone
                        else if (child2.Name.Equals("bone"))
                        {
                            ReadBone(key.hierarchy, child2);
                        }

                        // bone_ref
                        else if (child2.Name.Equals("bone_ref"))
                        {
                            ReadBoneRef(key.hierarchy, child2);
                        }
                    }
                }
            }
        }
        void ReadMainlineObject(XmlElement element, SpriterMainlineKey key)
        {
            SpriterMainlineObject obj = new SpriterMainlineObject();
            key.objects.Add(obj);

            Vector2 position = Vector2.zero, pivot = new Vector2(0, 1), scale = Vector2.one;
            Color color = Color.white;

            foreach(XmlAttribute attribute in element.Attributes)
            {
                // id
                if (attribute.Name.Equals("id"))
                    obj.ID = int.Parse(attribute.Value);

                // parent
                else if (attribute.Name.Equals("parent"))
                    obj.parent = int.Parse(attribute.Value);

                // object_type
                else if (attribute.Name.Equals("object_type"))
                {
                    obj.objectTypeRaw = attribute.Value;
                    obj.objectType = SpriterDataHelpers.ParseSpriterEnum<ObjectType>(obj.objectTypeRaw);
                }

                // atlas
                else if (attribute.Name.Equals("atlas"))
                    obj.atlas = int.Parse(attribute.Value);

                // folder
                else if (attribute.Name.Equals("folder"))
                    obj.folder = int.Parse(attribute.Value);

                // file
                else if (attribute.Name.Equals("file"))
                    obj.file = int.Parse(attribute.Value);

                // usage
                else if (attribute.Name.Equals("usage"))
                {
                    obj.usageRaw = attribute.Value;
                    obj.usage = SpriterDataHelpers.ParseSpriterEnum<UsageType>(obj.usageRaw);
                }

                // blend_mode
                else if (attribute.Name.Equals("blend_mode"))
                {
                    obj.blendModeRaw = attribute.Value;
                    obj.blendMode = SpriterDataHelpers.ParseSpriterEnum<BlendMode>(obj.blendModeRaw);
                }

                // name
                else if (attribute.Name.Equals("name"))
                    obj.name = attribute.Value;

                // x, y
                else if (attribute.Name.Equals("x"))
                    position.x = float.Parse(attribute.Value);
                else if (attribute.Name.Equals("y"))
                    position.y = float.Parse(attribute.Value);

                // pivot_x, pivot_y
                else if (attribute.Name.Equals("pivot_x"))
                    pivot.x = float.Parse(attribute.Value);
                else if (attribute.Name.Equals("pivot_y"))
                    pivot.y = float.Parse(attribute.Value);

                // angle
                else if (attribute.Name.Equals("angle"))
                    obj.angle = float.Parse(attribute.Value);

                // w, h
                else if (attribute.Name.Equals("w"))
                    obj.pixelWidth = int.Parse(attribute.Value);
                else if (attribute.Name.Equals("h"))
                    obj.pixelHeight = int.Parse(attribute.Value);

                // scale_x, scale_y
                else if (attribute.Name.Equals("scale_x"))
                    scale.x = float.Parse(attribute.Value);
                else if (attribute.Name.Equals("scale_y"))
                    scale.y = float.Parse(attribute.Value);

                // r, g, b, a
                else if (attribute.Name.Equals("r"))
                    color.r = float.Parse(attribute.Value);
                else if (attribute.Name.Equals("g"))
                    color.g = float.Parse(attribute.Value);
                else if (attribute.Name.Equals("b"))
                    color.b = float.Parse(attribute.Value);
                else if (attribute.Name.Equals("a"))
                    color.a = float.Parse(attribute.Value);

                // variable_type
                else if (attribute.Name.Equals("variable_type"))
                {
                    obj.variableTypeRaw = attribute.Value;
                    obj.variableType = SpriterDataHelpers.ParseSpriterEnum<VariableType>(obj.variableTypeRaw);
                }

                // value
                else if (attribute.Name.Equals("value"))
                    obj.value = ReadVariable(obj.variableType, attribute.Value);

                // min, max
                else if (attribute.Name.Equals("min"))
                    obj.min = ReadVariable(obj.variableType, attribute.Value);
                else if (attribute.Name.Equals("max"))
                    obj.max = ReadVariable(obj.variableType, attribute.Value);

                // animation
                else if (attribute.Name.Equals("animation"))
                    obj.entityAnimation = int.Parse(attribute.Value);

                // t
                else if (attribute.Name.Equals("t"))
                    obj.entityT = float.Parse(attribute.Value);

                // z_index
                else if (attribute.Name.Equals("z_index"))
                    obj.zIndex = int.Parse(attribute.Value);

                // volume
                else if (attribute.Name.Equals("volume"))
                    obj.volume = float.Parse(attribute.Value);

                // panning
                else if (attribute.Name.Equals("panning"))
                    obj.panning = float.Parse(attribute.Value);
            }

            foreach(XmlElement child in element)
            {
                // meta_data
                if (child.Name.Equals("meta_data"))
                    ReadMetaData(child, obj.metaData);
            }

            // Assign vector values
            obj.position = position;
            obj.pivot = pivot;
            obj.scale = scale;
            obj.color = color;

            // Object references
            obj.targetAtlas = m_Data.FindAtlas(obj.atlas);
            obj.targetFile = m_Data.FindFile(obj.folder, obj.file);
        }
        private void RecordFrame(SpriterAnimation anim, Transform characterRoot, SpriterMainlineKey keyframe,
		                         SpriterMainlineKey endKey, float currentTime, float frameRate,
		                         Dictionary<string, AnimationCurve>[] curves)
        {
            // Go through each sprite, keeping track of what is processed
            var processedSprites = new List<Component>();

            var bones = new Dictionary<int, Transform>();
            var scales = new Dictionary<int, Vector2>();

            foreach (SpriterMainlineBoneBase obj in keyframe.hierarchy.bones)
            {
                var obj1 = (obj as ISpriterTimelineBone);
                if (obj1 == null && obj is SpriterMainlineBoneRef)
                    obj1 = ((SpriterMainlineBoneRef)obj).target;
                if (obj1 == null)
                {
                    Debug.LogError("Unknown type");
                    continue;
                }

                SetObject(anim, characterRoot, keyframe == endKey, currentTime, frameRate, curves, processedSprites, obj1, bones, scales, obj);
            }

            //foreach(ISpriterSprite sprite in frame.sprites)
            foreach(SpriterMainlineObjectBase obj in keyframe.objects)
            {

                var obj1 = (obj as ISpriterTimelineObject);
                if (obj1 == null && obj is SpriterMainlineObjectRef)
                    obj1 = ((SpriterMainlineObjectRef)obj).target;
                if (obj1 == null)
                {
                    Debug.LogError("Unknown type");
                    continue;
                }

                SetObject(anim, characterRoot, keyframe == endKey, currentTime, frameRate, curves, processedSprites, obj1, bones, scales, null, obj);
            }

            //turn off all unused sprites
            foreach (var colorHelper in characterRoot.GetComponentsInChildren<SpriterNGUIColorHelper>())
            {
                //check if it's been used this keyframe
                if (!processedSprites.Contains(colorHelper))
                {
                    //grab the curve. If it doesn't exist, create it.
                    AnimationCurve val;
                    if (!curves[10].TryGetValue(GetRelativeName(colorHelper, characterRoot), out val))
                    {
                        val = new AnimationCurve();
                        curves[10].Add(GetRelativeName(colorHelper, characterRoot), val);
                    }

                    //If this is not the first keyframe, and the previous key was visible,
                    //make sure we step the value
                    int pos = val.AddKey(SteppedKeyframe(currentTime, 0));
                    if (pos > 0)
                    {
                        if (val.keys[pos - 1].value > 0)
                        {
                            val.AddKey(SteppedKeyframe(currentTime - (.001f / frameRate), val.keys[pos - 1].value));
                        }
                    }
                }
            }
        }