请选择 进入手机版 | 继续访问电脑版
查看: 1400|回复: 1

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

[复制链接]
发表于 2012-2-4 10:42:30 | 显示全部楼层 |阅读模式
   本节将运用前两节的知识到实际的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人物移动动画吗?其中有提到人物的移动基于它的左上角坐标,这是不真实的,那么我们需要为主角定义X,Y坐标,实现真实的定位到主角的脚底,所以我们这里需要一个逻辑:
        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; }
        }

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

定义好地图、障碍物和主角的坐标系以后,接着需要对主角和地图初始化:
        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.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.Y - SpiritCenterY * GridSize);
                    keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(cost * i));
                    keyFramesAnimationY.KeyFrames.Add(keyFrame);
                }

……
            }
        }

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


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


该贴已经同步到 LEE的微博
回复

使用道具 举报

发表于 2017-6-20 22:03:21 | 显示全部楼层
好贴,双手赞成!











腦筋急轉彎  冷笑話  心理測驗  名言佳句  小語 愛情小語  腦筋急轉彎  冷笑話  心理測驗  名言佳句  小語 愛情小語  勵志文章 勵志小語 勵志格言 勵志佳句 勵志小故事 勵志小品 勵志電影歌曲 勵志歌曲 勵志人生 勵志 勵志網 勵志文章 班級管理 成功勵志 大學生勵志 讀後感 讀書筆記 感恩勵志 聯考志願 高三勵志 觀後感 好詞好句 教育教學 親職教育 經典台詞 經典句子 經典語錄 勵志班會 創業 勵志電視劇 勵志電影 勵志歌曲 勵志故事 勵志獎學金 勵志教育 標語口號 勵志名言 勵志人物 日志大全 勵志詩歌 勵志書籍 勵志文章 勵志小說 勵志演講 勵志英語 經典美文 名人名言 青春勵志 人生感悟 傷感 詩詞名句 天有高中 為人處世 個性簽名 人生哲理 職場勵志 祝福語 資料大全 作文大全  勵志文章 勵志小語 勵志格言 勵志佳句 勵志小故事 勵志小品 勵志電影歌曲 勵志歌曲 勵志人生 勵志 勵志網 勵志文章 班級管理 成功勵志 大學生勵志 讀後感 讀書筆記 感恩勵志 聯考志願 高三勵志 觀後感 好詞好句 教育教學 親職教育 經典台詞 經典句子 經典語錄 勵志班會 創業 勵志電視劇 勵志電影 勵志歌曲 勵志故事 勵志獎學金 勵志教育 標語口號 勵志名言 勵志人物 日志大全 勵志詩歌 勵志書籍 勵志文章 勵志小說 勵志演講 勵志英語 經典美文 名人名言 青春勵志 人生感悟 傷感 詩詞名句 天有高中 為人處世 個性簽名 人生哲理 職場勵志 祝福語 資料大全 作文大全   2017年06月20日 勵志文章
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册 用百度帐号登录

本版积分规则

Archiver|手机版|小黑屋|BimCad Inc. ( 京ICP备15064117号

GMT+8, 2017-8-17 14:08 , Processed in 0.152799 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表