在前面诸多的章节里,我就地图构造的实现做了讲解,至此还遗留着一个关键问题:在游戏中是角色在移动还是地图在移动?它们之间的移动(位移)关系是如何实现的?

     那么在接下来的章节中我将围绕这两个问题进行详细的分析解说。

     首先,还得从游戏模式开始说起。目前2D俯视游戏中以即使战略、SLGRPG(ARPG)等类型的游戏为主流。在即时战略、SLG大地图中,地图的移动原理是:当鼠标处于游戏窗口的8个边缘时,地图即开始移动,我暂且称之为牵引式地图移动模式

     如上图,我们可以打这样一个比方:将游戏窗口比做我们的摄象机(上图中的Windows13窗口),在地图世界里不断的取景,我们从摄象机中看的只有摄象机镜头(游戏窗口)中能看到的区域(其他虚的地图部分窗口中是无法显示的)。但是游戏窗口又相当于是用支架固定着不能移动的镜头,那该如何才能看到景色的各个部位呢?那当然只有去移动背景地图图片让我们需要的景色部分呈现在窗口中。因此,根据上图描述的原理,当鼠标进入这8个区域(蓝色和棕色区域)时即触发地图的移动。

     为更方便大家的理解,我以窗口中左边那块蓝色区域为例:当我的鼠标在游戏窗口内往左移动快接近边缘时,此时地图图片就开始反向向右进行一定速度或加速度移动;同样的,地图中的所有对象均跟着地图图片以同样的方向与速度进行移动,这样给我们视觉上产生一种感觉:我们在通过鼠标牵引游戏窗口向左方去探寻地图及物体对象。有的朋友就要问了:如果一张地图上有1000个对象,难道我每10毫秒都要去移动这1000个对象吗?这样性能上说是完全不科学的!对,在实际开发中如果一张地图拥有大量物体对象的话,我们肯定不会这样做(如果地图是小地图,或者物体不多,这样做是完全可行的,并且更容易实现)。在理解了这个原理后,我们看看在WPF/Silverlight中是如何进行这些操作的。首先需要做的就在地图移动的时候,根据地图移动方向时时(在界面刷新线程CompositionTarget或间隔为10毫秒的DispatcherTimer)通过Foreach高性能的对所有物体对象(Spirit)的XY坐标进行修改;而什么时候才需要将这些物体对象显示出来呢?判断当前游戏窗口中心点对应的地图坐标点;并以该点为中点(圆心)进行一个矩形范围或半径为R的圆形范围(下文我简称为地图中心范围)搜索:如果某物体对象的XY坐标在此范围内则动态将它的显示实体加载它进入窗体画布(Carrier.Children.Add(Spirit)),然后再将之布局到它对应的XY坐标位置上(Canvas.setLeft(Spirit,X);Canvas.setTop(Spirit,Y);),并且继续根据地图的移动而移动(时时修改Canvas.setLeft(),Canvas.setTop());同样的,在地图移动中地图中心坐标是时时改变的,如果某些物体对象的XY坐标超出了地图中心范围,那么我们就将之从窗体画布中移除掉(Carrier.Children.Remove(Spirit)),此时这些物体对象相当于重新回到了等候显示的状态,它们的XY坐标同样在后台线程中时时更改,只要某个时候当地图中心再度出现在它们的附近时,它们又会重复以上的步骤再显示出来。

     大致原理有了,如何通过代码来具体实现呢?

     这里我提供两种方法:

     第一种方法为通过载体来实现地图移动。具体为首先向游戏窗体中添加8个完全透明的滚动介质(就好比图中那8块区域,其中4个蓝的,4个棕的)分别布局在地图边缘的8个位置上(它们相对于游戏窗体来说永远是不动的),然后在界面线程中时时判断鼠标是否悬停在它们中的某个上从而进行相应的地图移动。

     这里我以正下方的滚动介质为例,这样来创建它:

        int scrollspeed = 5; //定义滚动速度

        Rectangle roller = new Rectangle(); //创建滚动介质

        private void InitRoller() {

            roller.Width = 800;

            roller.Height = 20;

            roller.Opacity = 0.3;

            roller.Fill = new SolidColorBrush(Colors.Blue);

            Carrier.Children.Add(roller);

            Canvas.SetZIndex(roller, 10001);

            Canvas.SetTop(roller, 490);

        }

     这里为了演示需要,我将它的透明度暂且设置为0.3而不是0,目的是为方便大家可以看到它。接下来我们就需要在CompositionTargetTimer_Tick事件中时时判断鼠标是否在它上面:       

private void Timer_Tick(object sender, EventArgs e) {

            //时时判断如果鼠标停留在了该滚动介质上,则地图相应滚动

            if (roller.IsMouseOver) {

                Canvas.SetTop(Map, Canvas.GetTop(Map) - scrollspeed);

            }

   }

     这样就创建好了这8个区域中的其中一个(正下方区域),其他7个的创建和实现方法依次类推,很简单就不累述了。好了,我们按CTRL+F5来测试一下,当鼠标停留在窗体正下方的那个蓝色长方形区域时,地图会非常平滑的向上移动,这样就实现了我们视觉上的窗口向下取景效果。

     从上图可以看出,地图向上移从而实现窗口向下取景的视觉效果,这就是地图的相对移动原理。

     通过实体介质来实现地图移动的方式具有直观、代码简单、逻辑不复杂的特性,但性能不好。

     接下来看第二种方法,此方式不需要创建滚动介质,而是时时根据鼠标位置是否处于这8个区域中的任意一个进行对应的地图移动。这种方法相对于上一种方法来说虽然不够直观且需要的逻辑代码较多而繁,但它具有更高的性能与实用性,也是我推荐的方法。至于要如何实现它,我们首先需要写一个方法,该方法用来判断鼠标当前的位置并返回一个数字:

        //根据鼠标的位置获取鼠标所处的区域代号

        //0代表正上方区域(0点钟位置)然后其他7个区域按顺时针依次为1,2,3,4,5,6,7

        int distance = 80; //定义距离边缘多少即开始牵引地图

        private int getMouseArea() {

            Point MousePosition = Mouse.GetPosition(Carrier); //获取鼠标当前处于窗口中的位置

            int result = -1;

            //如果鼠标未超出窗口

            if (MousePosition.X >= 0 && MousePosition.Y >= 0) {

                //根据8种情况返回8个数字

                if (MousePosition.X >= 190 && MousePosition.X <= 570) {

                    if (MousePosition.Y <= distance) {

                        result = 0;

                    } else if (MousePosition.Y >= 500 - distance) {

                        result = 4;

                    }

                } else if (MousePosition.Y >= 125 && MousePosition.Y <= 375) {

                    if (MousePosition.X <= distance) {

                        result = 6;

                    } else if (MousePosition.X >= 760 - distance) {

                        result = 2;

                    }

                } else if ((MousePosition.X < 190 && MousePosition.Y <= distance)

|| (MousePosition.Y < 125 && MousePosition.X <= distance)) {

                    result = 7;

                } else if ((MousePosition.X > 570 && MousePosition.Y <= distance)

|| (MousePosition.Y < 125 && MousePosition.X >= 760 - distance)) {

                    result = 1;

                } else if ((MousePosition.X > 570 && MousePosition.Y >= 500 - distance)

|| (MousePosition.Y > 375 && MousePosition.X >= 760 - distance)) {

                    result = 3;

                } else if ((MousePosition.X < 190 && MousePosition.Y >= 500 - distance)

|| (MousePosition.Y > 375 && MousePosition.X <= distance)) {

                    result = 5;

                }

            }

            return result;

        }

     然后我们通过这个数字就可以对应地图边缘的8个区域看是需要将地图下移还是上移或是左上移动等等。这里需要注意一个地方,当地图已经移动到了某个方向的尽头时,地图是不能再移动的。所以综合以上,我们在Timer_Tick事件中这样来实现地图滚动:

        private void Timer_Tick(object sender, EventArgs e) {

            //第二种方法

            double mapleft = Canvas.GetLeft(Map);

            double maptop = Canvas.GetTop(Map);

            switch (getMouseArea()) {

                case 0:

                    if (maptop < 0) {

                        Canvas.SetTop(Map, Canvas.GetTop(Map) + scrollspeed);

                    }

                    break;

                case 1:

                    if (maptop < 0) {

                        Canvas.SetTop(Map, Canvas.GetTop(Map) + scrollspeed);

                    }

                    if (Map.Width + mapleft > this.ActualWidth) {

                        Canvas.SetLeft(Map, Canvas.GetLeft(Map) - scrollspeed);

                    }

                    break;

                case 2:

                    if (Map.Width + mapleft > this.ActualWidth) {

                        Canvas.SetLeft(Map, Canvas.GetLeft(Map) - scrollspeed);

                    }

                    break;

                case 3:

                    if (Map.Width + mapleft > this.ActualWidth) {

                        Canvas.SetLeft(Map, Canvas.GetLeft(Map) - scrollspeed);

                    }

                    if (Map.Height + maptop > this.ActualHeight) {

                        Canvas.SetTop(Map, Canvas.GetTop(Map) - scrollspeed);

                    }

                    break;

                case 4:

                    if (Map.Height + maptop > this.ActualHeight) {

                        Canvas.SetTop(Map, Canvas.GetTop(Map) - scrollspeed);

                    }

                    break;

                case 5:

                    if (Map.Height + maptop > this.ActualHeight) {

                        Canvas.SetTop(Map, Canvas.GetTop(Map) - scrollspeed);

                    }

                    if (mapleft < 0) {

                        Canvas.SetLeft(Map, Canvas.GetLeft(Map) + scrollspeed);

                    }

                    break;

                case 6:

                    if (mapleft < 0) {

                        Canvas.SetLeft(Map, Canvas.GetLeft(Map) + scrollspeed);

                    }

                    break;

                case 7:

                    if (maptop < 0) {

                        Canvas.SetTop(Map, Canvas.GetTop(Map) + scrollspeed);

                    }

                    if (mapleft < 0) {

                        Canvas.SetLeft(Map, Canvas.GetLeft(Map) + scrollspeed);

                    }

                    break;

            }

        }

     以上的代码主要就进行一些位置计算并判断,重复的部分很多并不复杂。最后大家可以按下CTRL+F5,嘿嘿!地图可以任意移动了。

     效果上来说地图是动了,可是主角还是始终处于窗口中的某一个位置保持不变(不管地图怎么移,它始终在窗口的左上角)。要实现跟随移动效果,这就需要我们根据前面所说的原理,在地图移动的同时对主角的XY坐标进行时时改变从而实现它的移动。关于主角如何在牵引式地图移动模式中的地图上移动及行走,我将在下一节进行详细的讲解,敬请关注。

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