目前地图编辑器已经能够对地图图片进行切片了,那么接下来我们需要做的是对切好的地图片进行拼装从而取代整张大地图。需要特别说明的是,如果一次性将所有的切片加载进游戏中并显示出来,那么效果与使用一张整的地图几乎没区别,性能上甚至可能会更差;本节我们最终要达到的目的是利用这些切片自适应的填充游戏窗口区域,即用最少的地图片实现窗口的无缝填充,从而提升游戏的整体性能。

教程示例游戏窗口模式时的窗体尺寸为800*600,那么我们首先根据此尺寸以400*300为单位利用地图编辑器切割出若干地图切片:

此时我们回过头来对照一下地图原图的容量与现在切好片的地图片的容量总和即会发现,在保持画质不变的前提下,切片后的总容量比之前减少了近一半。如果您做的是网络版,那么通过切片来实现按需下载,将缩短用户加载程序的时间同时降低服务器的负担:

接下来是关键了,游戏窗口800*600,地图切片每张为400*300,那么每次最少需要显示多少张切片才能在主角无论处于何位置时都能填满游戏窗口呢?大家不妨先看下图:

主角始终处于游戏窗口的正中,那么它距离左边400像素,距离顶端300像素,因此,我们通过边缘法可以得出,只需要根据主角的x,y坐标计算出主角当前所处的地图切片代号。以上图为例,此时主角所处的切片为2_3.jpg,那么我们只需加载2_3以及它周边的9张切片:1_2.jpg2_2.jpg3_2.jpg1_3.jpg2_3.jpg3_3.jpg1_4.jpg2_4.jpg3_4.jpg即可完美填充整个游戏窗口。当然,填充用地图切片必须是自适应的,即时时根据主角所处位置来更换所需要切片。

理解原理后,代码实现起来就简单多了,首先我们需要修改地图Surface初始化代码:

        Canvas MapSurface;

        int mapSectionWidth, mapSectionHeight, mapSectionXNum, mapSectionYNum;

        /// <summary>

        /// 初始化地图地表层

        /// </summary>

        private void InitMapSurface(XElement args) {

            MapSurface = new Canvas() {

                Width = Convert.ToDouble(args.Attribute("Width").Value),

                Height = Convert.ToDouble(args.Attribute("Height").Value),

            };

            Add(MapSurface);

            mapSectionWidth = Convert.ToInt32(args.Attribute("SectionWidth").Value);

            mapSectionHeight = Convert.ToInt32(args.Attribute("SectionHeight").Value);

            mapSectionXNum = (int)(MapSurface.Width / mapSectionWidth);

            mapSectionYNum = (int)(MapSurface.Height / mapSectionHeight);

        }

    然后我们定义两个非常重要的切片定位用变量:

        int _leaderSectionX;

        /// <summary>

        /// 主角所处的切片X

        /// </summary>

        public int leaderSectionX {

            get { return _leaderSectionX; }

            set { if (_leaderSectionX != value) { _leaderSectionX = value; ChangeMapSection(); } }

        }

        int _leaderSectionY;

        /// <summary>

        /// 主角所处的切片Y

        /// </summary>

        public int leaderSectionY {

            get { return _leaderSectionY; }

            set { if (_leaderSectionY != value) { _leaderSectionY = value; ChangeMapSection(); } }

        }

leaderSectionXleaderSectionY分别为主角所处的切片XY值(对应切片命名中下划线“_”两端的值),其中任意一个更改后都将激发ChangeMapSection方法:

        Image mapSection;

        int startSectionX, startSectionY,endSectionX,endSectionY;

        /// <summary>

        /// 更新呈现的地图切片

        /// </summary>

        private void ChangeMapSection() {

            MapSurface.Children.Clear();

            if (leaderSectionX == 0) {

                startSectionX = 0; endSectionX = 2;

            } else if (leaderSectionX == mapSectionXNum - 1) {

                startSectionX = leaderSectionX - 2; endSectionX = leaderSectionX;

            } else {

                startSectionX = leaderSectionX - 1; endSectionX = leaderSectionX + 1;

            }

            if (leaderSectionY == 0) {

                startSectionY = 0; endSectionY = 2;

            } else if (leaderSectionY == mapSectionYNum - 1) {

                startSectionY = leaderSectionY - 2; endSectionY = leaderSectionY;

            } else {

                startSectionY = leaderSectionY - 1; endSectionY = leaderSectionY + 1;

            }

            for (int x = startSectionX; x <= endSectionX; x++) {

                for (int y = startSectionY; y <= endSectionY; y++) {

                    mapSection = new Image() {

                        Source = Super.getImage(string.Format("Map/{0}/Surface/{1}_{2}.jpg", mapCode.ToString(), x, y)),

                        Width = mapSectionWidth,

                        Height = mapSectionHeight,

                        Stretch = Stretch.Fill,

                    };

                    MapSurface.Children.Add(mapSection);

                    Canvas.SetLeft(mapSection, x * mapSectionWidth);

                    Canvas.SetTop(mapSection, y * mapSectionHeight);

                }

            }

        }

这里我的处理是每次更换切片时都首先清除原有的9块切片,然后再添加新的9块切片到指定位置,终究是比较苯的办法,但实现起来简单;优化它的方法有两种:1)判断主角是向右移动到了新的切片上还是向左或是向下、向下,然后移除对应的3块再添加3块。2)将所有切片加载进地图中,只显示主角当前所处的9块切片,而其他的所有切片均处于Visibility.Collapsed状态不参加布局。此两种方法应该算得上最终解决方案,您可以根据您游戏的实际需要进行选择。

最后,在游戏的辅助线程中间隔检测主角所处的切片位置(由于有间隔,所以在区域转换时偶尔会出现白边的现象,如果接受不了,您完全可以将下面两行代码放到画面Loop中进行时时判断处理):

     leaderSectionX = (int)(Leader.X / mapSectionWidth);

     leaderSectionY = (int)(Leader.Y / mapSectionHeight);

嘿嘿~是不是很简单,且让我们先运行一下看看效果:

问题出来了,切片的边缘怎么也给显示了出来?难道是我们没有切割好吗?我将这些切片放进PS中重新对接,其实是可以完美吻合的,问题到底出在哪?希望好心的朋友告诉一下啦~。当然,这些缝隙是可以很轻易处理掉的,只需将每块切片的宽与高各加1即可:

……

mapSection = new Image() {

      Source = Super.getImage(string.Format("Map/{0}/Surface/{1}_{2}.jpg", mapCode.ToString(), x, y)),

      Width = mapSectionWidth + 1,

      Height = mapSectionHeight + 1,

      Stretch = Stretch.Fill,

};

……

修改后,我们再次运行游戏,当主角移动到右下角时,我们会看到两片白色无图区域:

难道又是BUGNONO。在上一节中我曾说过,地图编辑器只会按比例切割出同尺寸图片,如果某片的尺寸小于每片单位尺寸,则不切割。由于我们使用的示例地图图片的尺寸为2560*1920,因此以每片400*300为单位,只能切割出2400*1800的区域,这就是导致游戏中最终出现上图情况的原因了。

遇到这种情况我们该如何处理呢?最好的办法就是规范统一地图尺寸与切片尺寸:假设您的游戏窗口尺寸为800*600,切片单位为400*300,那么您的整张地图尺寸必须设定宽度为400的整倍数,高度为300的整倍数。另一种方案就是通过逻辑来呈现这些边缘不标准的切片。

地图自适应区域加载的实现,在一定程度上优化了游戏的整体性能,在地图尺寸越大的游戏中表现得更为突出。所有关于地图的教程快接近尾声了,下一节我将为大家讲解如何实现地图切换与传送,敬请关注。

WPF/Silverlight
作者:深蓝色右手
出处:http://alamiye010.cnblogs.com/
教程目录及源码下载:点击进入(欢迎加入WPF/Silverlight小组 WPF/Silverlight博客团队)
本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此段声明,且在文章页面显著位置给出原文连接,否则保留追究法律责任的权利。
posted on 2009-10-20 17:34  深蓝色右手  阅读(7203)  评论(14编辑  收藏  举报