本文将讨论三个话题:读写 XML 文件,读写二进制文件,自动探索。
游戏对象需要数据,但是我们不希望把对象和数据直接绑定起来,因为数据可能来自代码内部的字典,外部的文件,或者现场生成的随机数。我们可以在游戏对象和数据源之间搭建一条管道:
游戏对象调用某个方法与数据枢纽沟通,这个方法可能属于游戏对象,也可能属于数据枢纽。
我为 XML 数据和二进制数据建立了两条略有不同的管道:
XData 代表了一系列数据枢纽对象,它们从 LoadXML() 的返回值里抽取出一部分数据,供特定游戏对象使用。有些数据枢纽实现了 ISaveLoadXML 和/或 IGetData。
ISaveLoadXML 封装了 LoadXML(string path) 和 SaveXML(string path),这个接口提供的两个方法只能读写特定文件。IGetData 负责抽取数据。
我的 XML 文件通常包含两个节点(见图 2),可以使用 IGetData.GetIntData("ActorTag", "HP") 获得数据。
游戏对象 X 是 XML 数据管道的最后一站。它调用数据枢纽的方法获得数据,比方说:
所以说,看到游戏对象和数据源,我们的下一个问题肯定是:数据枢纽在哪里?万事皆三,巴佬们不会懂的。
SaveLoadFile.SaveBinary() 把一个数组 IDataTemplate[] 写入二进制文件。SaveLoadGame 收集游戏对象的数据,把类型转换成 IDataTemplate,传送给 SaveLoadFile。这里有一个关键点。我们不必序列化整个 Person 对象,因为它的 MaxHP,Name 等数据保存在 XML 文件里。不妨创建一个小对象 DTPerson,单独存放这个对象的 Salary。
那么怎样收集和发送数据呢?SaveLoadGame 包含一个私有数组 ISaveLoadBinary[] slb。游戏对象 Person 实现了接口 ISaveLoadBinary,把指向自己的引用保存在 slb 里面。接口定义见图 8。
保存游戏的时候,我们遍历 slb 的每一个元素,调用 Save(out IDataTemplate data) 收集数据。Person 的 Save() 定义如下:
读取游戏存档的第一步是调用 SaveLoadFile.LoadBinary(),把返回值放入临时变量 IDataTemplate[] load。接下来,我们遍历 load 的每一个元素,根据 IDataTemplate.DTTag 调用不同对象的 ISaveLoadBinary.Load()。请看一个简单的例子:
首先来看一下 AutoExplore 的方法 `public int[] GetDestination()`。
在 ResetBoard() 内部,我们首先生成一个和地下城一样大的二维数组;然后定义三个特殊的距离;接下来遍历二维数组的每个元素,设置初始距离,记录起始位置;最后返回这个二维数组。
三个判断条件顺序不能弄错。起始位置未必是一方通行的。比方说,白色相簿试图接近米④达,把后者所在位置标记为起始点,但这个位置是被占据的,因此无法通过。
GetDestination() 的第二步很简单,我们直接看第三步。SetDistance(int[,] dungeon, Stack<int[]> start) 递归地标记出所有未探索(unexplored)格子的距离。
上述代码里出现了两个方法:GetNeighbor(int[] center) 和 GetDistance(int[] center)。前者返回 center 周围八个格子的坐标,后者做了三件事情:
GetDestination() 的最后一步是找到与当前演员相邻、并且距离最小的格子。如果有多个距离相等的格子,随机选择一个。
以上是本月总结。最后留一道思考题。请结合创作时间(1995,2006 和 2017),分析以下三幅画面的镜头语言。
评论区
共 13 条评论热门最新