Silverlight数学引擎(15)——数据存取


这几天年终绩效考核,琐事比较多所以更新得没有以前快了,绝对不是因为网上说的什么微软关闭Silverlight网站的影响啦,就算微软说现在马上抛弃SL,我也要把这个游戏写完,对于那些一边玩着Windows系统,一边大骂.net framework是微软的骷髅的人我一般也没有什么可说的。

说到游戏,数据存取这个功能是必须的,不管是游戏设置还是状态什么的,都是可以存成数据文件,然后可以从文件加载。本节就来实现将我们画的图形存成Xml文件,然后可以从Xml文件中加载图形,也只有这样,我们的过关游戏才能实现。

要实现数据保存就要现有文件的内容格式,对于我们的坐标系上各个图形元素而言,就是要把他们按照依赖顺序写到Xml中,这样才能保存加载的时候才能不出意外,因此需要做的事情有以下几点:

  1.一个坐标系中的每个图形都必须有唯一的ID或者Name

 2.必须按照图形被添加到坐标系的顺序保存。

 3.必须记录每个图形的上层依赖(比如线AB依赖于点A和点B)

 4.图形的属性也需要保存(例如交点索引Index以及样式等)

 综合以上,我们可以给出一个Xml文件格式:

<?xml version="1.0" encoding="utf-16"?>
<CS>
    <Shapes>
        <FreePoint Name="A" Style="00,11,1.00,1.00,False" P="1.00,2.00"/>
        <FreePoint Name="B" Style="00,11,1.00,1.00,False" P="-2.00,3.00"/>
        <FreePoint Name="C" Style="00,11,1.00,1.00,False" P="0.00,0.00"/>
        <LineShape Name="AB" Style="08,00,4.00,1.00,False" D="A,B"/>
        <LineShape Name="BC" Style="08,00,4.00,1.00,False" D="B,C"/>
        <LineShape Name="CA" Style="08,00,4.00,1.00,False" D="C,A"/>
        <CircleShape Name="OA" Style="00,02,0.50,1.00,True" D="A,A,B"/>
        <CircleShape Name="OB" Style="00,02,0.50,1.00,True" D="B,A,B"/>
        <IntersectionPointOfCircles Name="D" Style="00,08,0.50,1.00,True" D="OA,OB" I="1"/>
        <IntersectionPointOfCircles Name="E" Style="00,08,0.50,1.00,True" D="OA,OB" I="2"/>
        <LineShape Name="DE" Style="08,00,0.50,1.00,True" D="D,E"/>
        <CircleShape Name="OB1" Style="00,02,0.50,1.00,True" D="B,B,C"/>
        <CircleShape Name="OC" Style="00,02,0.50,1.00,True" D="C,B,C"/>
        <IntersectionPointOfCircles Name="F" Style="00,08,0.50,1.00,True" D="OB1,OC" I="1"/>
        <IntersectionPointOfCircles Name="G" Style="00,08,0.50,1.00,True" D="OB1,OC" I="2"/>
        <LineShape Name="FG" Style="08,00,0.50,1.00,True" D="F,G"/>
        <IntersectionPointOfLines Name="H" Style="00,08,1.00,1.00,False" D="DE,FG"/>
        <LineShape Name="HA" Style="08,00,0.50,1.00,True" D="H,A"/>
        <CircleShape Name="OH" Style="00,02,2.00,1.00,False" D="H,H,A"/>
    </Shapes>
</CS>

Xml的好处是有目共睹的,看到上面的Xml格式,你是否对数据保存的功能实现胸有成竹了呢?当然,你可以已经发现了还有些细节工作需要做的:

第一,我们要格式化样式:

        public static string GetStyleString(Shape shape)
        {
            var result = string.Format("{0},{1},{2},{3},{4}",
                                       BrushPicker.GetBrushId(shape.Stroke as SolidColorBrush).ToString("00"),
                                       BrushPicker.GetBrushId(shape.Fill as SolidColorBrush).ToString("00"),
                                       shape.StrokeThickness.ToString("0.00"),
                                       shape.Opacity.ToString("0.00"),
                                       shape.StrokeDashArray.Count > 0);
            return result;
        }

第二我们要添加图形的上层依赖列表(Parents):

        public List<ICoordinate> Parents { get; set; }
        public void RegisterDependencies(params ICoordinate[] parents)
        {
            if (Parents == null)
                Parents = new List<ICoordinate>();
            foreach (var parent in parents)
            {
                Parents.Add(parent);
                if (parent.Children == null)
                    parent.Children = new List<ICoordinate>();
                parent.Children.Add(this);
            }
        }

第三我们要在ICoordinate中添加新的接口WriteXmlReadXml,这两个方法的功能恰好相反,因此放在一起实现比较直观,例如FreePoint中时这样实现的这两个方法的:

        public override void WriteXml(XmlWriter writer)
        {
            base.WriteXml(writer);
            writer.WriteAttributeString("P", Center.X.ToString("0.00") + "," + Center.Y.ToString("0.00"));
        }

        public override void ReadXml(XElement element)
        {
            base.ReadXml(element);
            var ps = element.ReadString("P").Split(',');
            Center.SetPosition(double.Parse(ps[0]), double.Parse(ps[1]));
        }

OK,这些工作完成后,数据保存就十分简单了,如果你对XML文件比较熟悉的话:

        private static void Write(CoordinateSystem cs, XmlWriter writer)
        {
            if (cs.Shapes.Count == 0)
                return;

            writer.WriteStartElement("CS");
            writer.WriteStartElement("Shapes");
            foreach (var shape in cs.Shapes)
            {
                writer.WriteStartElement(shape.GetType().Name);
                writer.WriteAttributeString("Name", shape.Name);
                writer.WriteAttributeString("Style", ShapeStyleEditor.GetStyleString(shape.Shape));
                if (shape.Parents != null)
                    writer.WriteAttributeString("D", shape.Parents.Select(d => d.Name).ToListString(","));
                shape.WriteXml(writer);
                writer.WriteEndElement();
            }
            writer.WriteEndElement(); //Shapes
            writer.WriteEndElement(); //CS
        }

数据保存成XML看来比较简单,反过来解析就不那么容易了,在此我们需要用到反射机制,而反射的实例创建是调用默认的无参构造函数创建的,如:

var shape = (ICoordinate) Activator.CreateInstance(typeof(FreePoint));

但是我们定义的所有图形都是严格按照依赖创建的,也就是说没有无参的构造函数,如果直接使用我们现有的方法把Xml解析成Shapes,只能使用无穷的if else if else了,这无疑是非常不雅的,为了实现高度自治,我们还是为每个Shape类实现无参的构造函数吧,有点麻烦,但是长痛不如短痛。

首先为了能够在构造函数中使用Xml节点等一些信息,我们需要定义一个静态的类ShapeSerializer

    public static class ShapeSerializer
    {
        private static CoordinateSystem CS { get; set; }
        private static XElement XmlNode { get; set; }
        public static int Index { get { return (int)XmlNode.ReadDouble("I"); } }
        public static double Ratio { get { return XmlNode.ReadDouble("R"); } }
        public static string Style { get { return XmlNode.ReadString("Style"); } }
        public static List<ICoordinate> Parents { get; private set; }
}

这样就能方便的实现无参构造函数了,拿LineShape举例吧:

    public sealed class LineShape : CoordinateBase
    {
        public LineShape()
            : this(ShapeSerializer.Parents[0] as PointShape, ShapeSerializer.Parents[1] as PointShape) { }

        public LineShape(PointShape p1, PointShape p2)
        {
            Line = new LogicalLine(p1.Center, p2.Center);
            P1 = p1;
            P2 = p2;
            RegisterDependencies(p1, p2);
        }
}

我们把完整的解析代码页放在ShapeSerializer类中,如下:

        public static void Parse(string xml, CoordinateSystem cs)
        {
            try
            {
                CS = cs;
                CS.Children.Clear();
                var element = XElement.Parse(xml);
                var figuresNode = element.Element("Shapes");
                var types = ReflectorUtils.DiscoverTypes<CoordinateBase>();
                if (figuresNode != null)
                {
                    foreach (var figureNode in figuresNode.Elements())
                    {
                        var type = types.FirstOrDefault(t => t.Name == figureNode.Name);
                        if (type == null)
                            throw new Exception("Type not found:" + figureNode.Name);
                        XmlNode = figureNode;
                        Parents = GetParents();
                        var shape = (ICoordinate) Activator.CreateInstance(type);
                        shape.Name = figureNode.ReadString("Name");
                        shape.CS = CS;
                        shape.ReadXml(XmlNode);
                        ShapeStyleEditor.ApplyStyle(shape.Shape, Style);
                    }
                }
                cs.Shapes.OfType<IMovable>().ToList().ForEach(m => m.Move(((ICoordinate) m).Center));
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

这样,整个保存和解析的功能就完成了,如何测试呢?对了,我们不是有了右键菜单吗,我们再加两个菜单选项【复制】和【粘贴】,分别实现将内容保存到剪贴板和从剪贴板加载图形,这样就能很好地测试了,而且还省去了打开和保存文件那么麻烦。

运行效果如图:

【源代码和演示地址】

 http://www.diyuexi.com/webpages/query/ShareRes.aspx

 大功告成,算是完成了里程碑的一页!前面讲到,我的一个目的就是做一个几何作图的过关游戏,根据过关的等级显示不同的技能,比如初始技能只有【笔】、【尺】、【规】,当你过了用这些初始竟能画平行线这一关,就会多一个技能【平行线】,所以以后就可以更方便地去画平行线了,下节我们就来看看如何实现【平行线】这类组合技能的实现!

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM