C#开发WPFSilverlight动画及游戏系列教程(Game Tutorial):(九) 2D游戏角色在地图上的移动

    技术2022-05-12  17

        本节将运用前两节的知识到实际的2D游戏人物在地图上移动中,同时也算是对前面八节的内容进行一次综合运用吧。

        那么先从最底层的地图讲起。首先我将一张地图添加进游戏窗口中,这里我同样使用Image控件:

            Image Map = new Image();

            private void InitMap() {

                Map.Width = 800;

                Map.Height = 600;

                Map.Source = new BitmapImage((new Uri(@"Map/Map.jpg", UriKind.Relative)));

                Carrier.Children.Add(Map);

                Map.SetValue(Canvas.ZIndexProperty, -1);

            }

        我将一个800*600名叫Map.jpg的地图图片添加进项目Map文件夹中,然后将它的Canvas.Zindex属性设置为-1,这样它就相当于地图背景的作用了。有了这张地图以后,我们需要对它进行障碍物设置:

     

        从上图可以看到,理想的状态下,障碍物为我用蓝色填充的区域,这是理想状态下障碍物的设置。但是实际运用中,就拿本教程来讲,因为GridSize设置为20,那么我们最终得到的障碍物将是这样的:

     

        从上图可以看到,每个绿色格子代表一个20*20像素的障碍物,只能说勉强达到描绘障碍物的效果吧。从而又验证了我们上一节所讲到的GridSize越小,定位将越精确,难道不是至理名言吗!

        有了这个思路,接下来我用了3个循环算法实现了左部分的障碍物设定:

                //构建障碍物

                for (int y = 12; y <= 27; y++) {

                    for (int x = 0; x <= 7; x++) {

                        //障碍物在矩阵中用0表示

                        Matrix[x, y] = 0;

                        rect = new Rectangle();

                        rect.Fill = new SolidColorBrush(Colors.GreenYellow);

                        rect.Opacity = 0.3;

                        rect.Stroke = new SolidColorBrush(Colors.Gray);

                        rect.Width = GridSize;

                        rect.Height = GridSize;

                        Carrier.Children.Add(rect);

                        Canvas.SetLeft(rect, x * GridSize);

                        Canvas.SetTop(rect, y * GridSize);

                    }

                }

                int move = 0;

                for (int x = 8; x <= 15; x++) {

                    for (int y = 12; y <= 18; y++) {

                        Matrix[x, y - move] = 0;

                        rect = new Rectangle();

                        rect.Fill = new SolidColorBrush(Colors.GreenYellow);

                        rect.Opacity = 0.3;

                        rect.Stroke = new SolidColorBrush(Colors.Gray);

                        rect.Width = GridSize;

                        rect.Height = GridSize;

                        Carrier.Children.Add(rect);

                        Canvas.SetLeft(rect, x * GridSize);

                        Canvas.SetTop(rect, (y - move) * GridSize);

                    }

                    move = x % 2 == 0 ? move + 1 : move;

                }

                int start_y = 4;

                int end_y = 10;

                for (int x = 16; x <= 23; x++) {

                    for (int y = start_y; y <= end_y; y++) {

                        Matrix[x, y + move] = 0;

                        rect = new Rectangle();

                        rect.Fill = new SolidColorBrush(Colors.GreenYellow);

                        rect.Opacity = 0.3;

                        rect.Stroke = new SolidColorBrush(Colors.Gray);

                        rect.Width = GridSize;

                        rect.Height = GridSize;

                        Carrier.Children.Add(rect);

                        Canvas.SetLeft(rect, x * GridSize);

                        Canvas.SetTop(rect, (y + move) * GridSize);

                    }

                    start_y = x % 3 == 0 ? start_y + 1 : start_y;

                    end_y = x % 3 == 0 ? end_y - 1 : end_y;

                }

        构建好障碍物后运行程序测试的效果如下图:

     

        障碍物终于绘制完毕了,那么接下来就是动画部分了。还记得我们第六章中实现2D人物移动动画吗?其中有提到人物的移动基于它的左上角坐标,这是不真实的,那么我们需要为主角定义XY坐标,实现真实的定位到主角的脚底,所以我们这里需要一个逻辑:

            int count = 1;

            Image Spirit = new Image(); //创建主角

            int SpiritCenterX = 4; //主角脚底离主角图片左边的距离(游戏坐标系中)

            int SpiritCenterY = 5; //主角脚底离主角顶部的距离(游戏坐标系中)

            //游戏坐标系中Spirit坐标(缩小操作)

            int _SpiritGameX;

            int SpiritGameX {

                get { return ((int)Canvas.GetLeft(Spirit) / GridSize) + SpiritCenterX; }

                set { _SpiritGameX = value; }

            }

            int _SpiritGameY;

            int SpiritGameY {

                get { return ((int)Canvas.GetTop(Spirit) / GridSize) + SpiritCenterY; }

                set { _SpiritGameY = value; }

            }

            //窗口坐标系中Spirit坐标(放大操作)

            int SpiritWindowX {

                get { return (SpiritGameX - SpiritCenterX) * GridSize; }

            }

            int SpiritWindowY {

                get { return (SpiritGameY - SpiritCenterY) * GridSize; }

            }

        上一节有说到关于两个不同坐标系同时存在的问题,上面的代码就是对它们的定义并且实现它们之间相互转换,设置好以后,就可以根据情况的需要来分别调用不同坐标系下主角的XY坐标了。

      定义好地图、障碍物和主角的坐标系以后,接着需要对主角和地图初始化:

            public Window9() {

                InitializeComponent();

                ResetMatrix(); //初始化二维矩阵

                InitPlayer(); //初始化目标对象

                InitMap(); //初始化地图

     

                DispatcherTimer dispatcherTimer = new DispatcherTimer();

                dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);

                dispatcherTimer.Interval = TimeSpan.FromMilliseconds(150);

                dispatcherTimer.Start();

            }

        可以看到后面4行代码那么的眼熟?其实就是第三节所讲到的知识。最后就是本节的重头戏,实现鼠标点击事件:

            private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {

                Point p = e.GetPosition(Carrier);

                //进行坐标系缩小

                int start_x = SpiritGameX;

                int start_y = SpiritGameY;

                Start = new System.Drawing.Point(start_x, start_y); //设置起点坐标

                int end_x = (int)p.X / GridSize;

                int end_y = (int)p.Y / GridSize;

                End = new System.Drawing.Point(end_x, end_y); //设置终点坐标

    ……

                if (path == null) {

                    MessageBox.Show("路径不存在!");

                } else {

    ……

                    for (int i = 0; i < framePosition.Count(); i++) {

                        //加入X轴方向的匀速关键帧

                        LinearDoubleKeyFrame keyFrame = new LinearDoubleKeyFrame();

                        //平滑衔接动画

                        keyFrame.Value = i == 0 ? Canvas.GetLeft(Spirit) : (framePosition[i].X - SpiritCenterX * GridSize);

                        keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(cost * i));

                        keyFramesAnimationX.KeyFrames.Add(keyFrame);

                        //加入X轴方向的匀速关键帧

                        keyFrame = new LinearDoubleKeyFrame();

                        keyFrame.Value = i == 0 ? Canvas.GetTop(Spirit): (framePosition[i].Y - SpiritCenterY * GridSize);

                        keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(cost * i));

                        keyFramesAnimationY.KeyFrames.Add(keyFrame);

                    }

                    ……

                }

            }

        代码和上一节里的没有多大的区别,改动为我用黄色背景色描绘的区域(…….号表示该段代码与上一节不变)。主要就是针对如何进行主角真实脚底坐标在两个坐标系中的换算问题进行了布局修改,大家可以与上一节里的示例代码进行比较,非常容易就可以进行分析理解,这里我就不再累述了。大功告成啦,我将障碍物的表现去掉,然后国际惯例Ctrl+F5测试一下我们的成果吧:

     

        至此,我们就实现了2D游戏人物在地图中的移动。大家再回头看看或许会发现:本节地图中的障碍物均是由正方形块组成,也就是说地图是基于直角坐标系的。但是在实际的游戏制作中,特别是SLG走格子回合制等类型的游戏中,基本都采用斜度的地图构造。那么下一节我将就如何构造斜度坐标系地图进行讲解,敬请关注。

    作者:深蓝色右手 出处: http://blog.csdn.net/alamiye010/ 教程目录及源码下载: 点击进入 本文版权归作者和共有,欢迎转载。但未经作者同意必须保留此段声明,且在文章页面显著位置给出原文连接,否则保留追究法律责任的权利。

    最新回复(0)