VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Objective-C编程 >
  • C#开发WPF Silverlight动游戏教程之地图编辑器

制作者:剑锋冷月 单位:无忧统计网,www.51stat.net
 

  到目前为止,教程示例游戏中虽然实现了A*,但是还无法轻松的为地图设置障碍物;并且游戏所有地图均为一张整的大图片,主角的移动会导致窗体对地图的不停切割,越大的地图带来的负面性能损耗越明显。对地图进行切片处理则可达到性能的最大优化:载入的时候按需加载,地图根据主角的位置仅显示特定部份;并且如果还能配上任意勾勒的遮挡物,那么这一切的一切将更能完美的诠释我们的游戏。开发制作地图编辑器已迫在眉睫。

  那么本节我将为大家讲解如何制作一款基于Grid的即易用又强大的地图编辑器,并首先实现障碍物设定功能及A*寻路模拟。

  第一步:设计布局

  通用型的编辑器必须能够适应所有尺寸的地图,因此我选择ScrollViewer作为地图的承载容器,并通过设置它的HorizontalScrollBarVisibility与VerticalScrollBarVisibility均为auto使之能自适应地图尺寸,即当地图超出窗体尺寸的情况下出现滚动条。由于一个ScrollViewer只能承载一个Content,因此为了可以在其上面能够进行障碍物的绘制及擦除操作,我们还必须添加另外一个背景透明的ScrollViewer用于承载障碍物网格(Grid):

  如描述上说的,这两个ScrollViewer必须保持协动一致,即两者的尺寸、滚动条时刻所处的位置等均表现一致:

        //滚动窗体二级协动
        private void ObstructionViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) {
            ScrollViewer scrollViewer = sender as ScrollViewer;
            MapViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset);
            MapViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset);
        }

  •  
 

  第二步,设计功能

  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;
            }
        }

 

  至于这些障碍物数据该如何才能为本教程示例游戏所用?嘿嘿~且听下回分解。

  地图编辑器通过以上的构造及功能设置已初具雏形,但是离真正完整功能的编辑器还是有着非常大的距离。后续教程中我会根据需要,在此编辑器的基础上不断添加新功能,目的只有一个:使游戏设计更轻松,更快速。一定要关注哦!

 

 

  第二步,设计功能

  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;
……

 



相关教程