没有地图编辑器的游戏不是好游戏--至理明言。
到目前为止,教程示例游戏中虽然实现了A*,但是还无法轻松的为地图设置障碍物;并且游戏所有地图均为一张整的大图片,主角的移动会导致窗体对地图的不停切割,越大的地图带来的负面性能损耗越明显。对地图进行切片处理则可达到性能的最大优化:载入的时候按需加载,地图根据主角的位置仅显示特定部份;并且如果还能配上任意勾勒的遮挡物,那么这一切的一切将更能完美的诠释我们的游戏。开发制作地图编辑器已迫在眉睫。
那么本节我将为大家讲解如何制作一款基于Grid的即易用又强大的地图编辑器,并首先实现障碍物设定功能及A*寻路模拟。
第一步:设计布局
通用型的编辑器必须能够适应所有尺寸的地图,因此我选择ScrollViewer作为地图的承载容器,并通过设置它的HorizontalScrollBarVisibility与VerticalScrollBarVisibility均为auto使之能自适应地图尺寸,即当地图超出窗体尺寸的情况下出现滚动条。以ScrollViewer作为载体,它将承载本地图编辑器中非常重要的3个画板,分别为:障碍物画板,障碍物单元格边线画板和地图切片边缘线画板:
根据上图的结构,最终界面上ScrollViewer的xaml定义如下:
<ScrollViewer x:Name="MapViewer" Width="755" Height="568" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" PreviewMouseMove="ObstructionViewer_PreviewMouseMove" PreviewMouseLeftButtonDown="ObstructionViewer_PreviewMouseLeftButtonDown">
<ScrollViewer.Content>
<Canvas x:Name="Map">
<Canvas x:Name="GridCarrier" Opacity="0.4"></Canvas>
<Canvas x:Name="GridLineCarrier" Opacity="0.4"></Canvas>
<Canvas x:Name="SectionCarrier" Opacity="0.9" Visibility="Collapsed"></Canvas>
</Canvas>
</ScrollViewer.Content>
</ScrollViewer>
第二步,设计功能
1)载入地图:
通过OpenFileDialog来开启一个文件选择对话框,并通过文件选择过滤器Filter限制加载图片的类型为*.jpg和*.png:
//载入地图
private void LoadMap_Click(object sender, RoutedEventArgs e) {
OpenFileDialog loadMap = new OpenFileDialog() {
CheckFileExists = true,
CheckPathExists = true,
Multiselect = false,
Filter = "图像文件(*.jpg,*.png)|*.jpg;*.png",
};
loadMap.FileOk += new System.ComponentModel.CancelEventHandler(loadMap_FileOk);
loadMap.ShowDialog();
}
2)布局网格:
为了可以适应不同GridSize尺寸的需要,我们需要更加灵活的网格模型,因此选择Grid做为主体,当加载完地图后,我们可以根据该地图的相关数据自动在画板ScrollViewer中动态添加Grid(包括行数和列数):
……
grid = new Grid() {
ShowGridLines = ShowGrid.IsChecked.Value,
Width = width,
Height = height,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
};
GridWidth.Text = gridWidth.ToString();
GridHeight.Text = gridHeight.ToString();
for (int x = 0; x < grid.Width / gridWidth; x++) {
ColumnDefinition col = new ColumnDefinition() {
Width = new GridLength(gridWidth),
};
grid.ColumnDefinitions.Add(col);
}
for (int y = 0; y < grid.Height / gridHeight; y++) {
RowDefinition row = new RowDefinition() {
Height = new GridLength(gridHeight),
};
grid.RowDefinitions.Add(row);
}
scrollViewer.Content = grid;
……
Grid的ShowGridLines参数非常有意思,通过将之设置为True即可显示出Grid所有单元格的边框:
遗憾的是,一旦启动了网格边框显示,将严重影响界面线程的性能,仿佛有些鸡肋了,有时间我还打算尝试其他的方式来高效的设置单元格边框。
3)设置障碍物:
通过为画板ScrollViewer注册鼠标左键点击事件及鼠标移动事件并配合一定的逻辑来实现障碍物的绘制于擦除:
…
if (grid == null) { return; }
Point p = e.GetPosition(ObstructionViewer);
if (p.X < 738 && p.Y < 551) {
p = e.GetPosition(Map);
test.Text = string.Format("当前坐标 x:{0} y:{1}", (int)p.X, (int)p.Y);
SetObstructionMatrix((int)(p.X / GridWidthSlider.Value), (int)(p.Y / GridHeightSlider.Value), 0);
}
}
…
4)模拟A*寻路:
在画板上描绘完障碍物后,再通过自行绘制起点与终点,并将教程中的A*寻路dll引用到本编辑器中即可以实现A*寻路模拟:
IPathFinder pathFinder;
List<Rectangle> pathRect = new List<Rectangle>();
//模拟A*寻路
private void FindPath_Click(object sender, RoutedEventArgs e) {
if (grid == null || start == "" || end == "") { return; }
string[] str = start.Split('_');
int start_x = Convert.ToInt32(str[1]);
int start_y = Convert.ToInt32(str[2]);
str = end.Split('_');
int end_x = Convert.ToInt32(str[1]);
int end_y = Convert.ToInt32(str[2]);
pathFinder = new PathFinderFast(ObstructionMatrix);
List<PathFinderNode> path = pathFinder.FindPath(new Point(start_x, start_y), new Point(end_x, end_y));
if (path == null) {
MessageBox.Show("路径不存在!");
} else {
textBlock3.Text = string.Format("耗时:{0}秒", Math.Round(pathFinder.CompletedTime, 8).ToString());
string result = "";
RemoveRect();
for (int i = 0; i < path.Count; i++) {
result += string.Format("{0}_{1}", path[i].X, path[i].Y);
SetRect(new SolidColorBrush(Colors.White), new SolidColorBrush(Colors.Black), GridWidthSlider.Value * 2 / 3, GridHeightSlider.Value * 2 / 3, GridWidthSlider.Value * 2 / 3, GridHeightSlider.Value * 2 / 3, path[i].X, path[i].Y);
}
}
}
4)障碍物数组的导出与导入:
我们可以事先制作好一个xml模板用于存放地图中的障碍物信息:
<?xml version="1.0" encoding="utf-8" ?>
<Item ID="Obstruction" Value="" />
当绘制出满意的地图障碍物并通过A*模拟测试无误后即可将此时的障碍物数组信息进行导出保存:
//导出障碍物信息文件
private void outputMatrix_FileOk(object sender, CancelEventArgs e) {
SaveFileDialog outputMatrix = sender as SaveFileDialog;
string result = "";
for (int y = 0; y <= ObstructionMatrix.GetUpperBound(1); y++) {
for (int x = 0; x <= ObstructionMatrix.GetUpperBound(0); x++) {
if (ObstructionMatrix[x, y] == 0) {
result = string.Format("{0}{1}", result, string.Format("{0}_{1},", x, y));
}
}
}
SetXmlValue(Data, "Item", "ID", "Obstruction", "Value", result.TrimEnd(','));
Data.Save(outputMatrix.FileName);
MessageBox.Show("导出成功!");
}
以上图为例,该图中的障碍物信息导出后的文件内容如下:
这些障碍物数据以x_y的形式命名,并以,号间隔,因此对其重新载入也是非常容易的事:
//导入障碍物信息文件
private void loadMatrix_FileOk(object sender, CancelEventArgs e) {
OpenFileDialog loadMatrix = sender as OpenFileDialog;
try{
XElement xml = XElement.Load(string.Format(@"{0}", loadMatrix.FileName));
if (xml.HasAttributes) {
ClearGrid();
RemoveRect();
string[] matrix = GetXmlValue(xml, "Item", "ID", "Obstruction", "Value").Split(',');
for (int i = 0; i < matrix.Count(); i++) {
SetRect(string.Format("Rect_{0}", matrix[i]), new SolidColorBrush(Colors.Yellow), new SolidColorBrush(Colors.Black), GridWidthSlider.Value, GridHeightSlider.Value, 0, 0, Convert.ToInt32(matrix[i].Split('_')[0]), Convert.ToInt32(matrix[i].Split('_')[1]));
}
}
} catch {
MessageBox.Show("导入失败!请检文件是否匹配");
e.Cancel = true;
}
}
至于这些障碍物数据该如何才能为本教程示例游戏所用?嘿嘿~且听下回分解。
地图编辑器通过以上的构造及功能设置已初具雏形,但是离真正完整功能的编辑器还是有着非常大的距离。后续教程中我会根据需要,在此编辑器的基础上不断添加新功能,目的只有一个:使游戏设计更轻松,更快速。一定要关注哦!(该编辑器将时时更新新功能,源码请到目录中下载)
出处:http://alamiye010.cnblogs.com/
教程目录及源码下载:点击进入(欢迎加入WPF/Silverlight小组 WPF/Silverlight博客团队)
本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此段声明,且在文章页面显著位置给出原文连接,否则保留追究法律责任的权利。