美文网首页WPF技术精选案例
好玩且可自定义的迷宫游戏!

好玩且可自定义的迷宫游戏!

作者: X_Zero | 来源:发表于2022-04-19 16:56 被阅读0次

效果预览

MazeAnimation.gif

技术需求

要做一个可以高度自定义的迷宫游戏

技术框架

基于.net 4.6 的技术框架
基于wpf的客户端开发框架
基于HandyControl的UI框架

主要功能技术实现

1.创建迷宫主界面

在主窗体中加入小球、迷宫地图容器、迷宫出口、遮罩阴影等元素

<Window x:Class="Maze.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Maze"
        mc:Ignorable="d"
        Title="MainWindow" Height="1000" Width="1000" Background="Transparent" AllowsTransparency="True" WindowStyle="None" WindowStartupLocation="CenterScreen" SizeChanged="Window_SizeChanged" PreviewKeyDown="Window_PreviewKeyDown" Loaded="Window_Loaded">

    <Window.Resources>
        <!--迷宫出口-->
        <PathGeometry x:Key="pg" Figures="M 150,100 L 100,100 L 100,900 L 800,900 L 800,920 M 850,920 L 850,900 L 900,900 L 900,100 L 100,100" />
        <ContextMenu x:Key="ContextMenu" MenuItem.Click="ContextMenu_Click">
            <MenuItem Header="删除"/>
            <MenuItem Header="复制"/>
        </ContextMenu>
    </Window.Resources>

    <Grid>
        <Border Background="White" CornerRadius="8">
            <Border.ToolTip>
                <Grid></Grid>
            </Border.ToolTip>
            <Border.Effect>
                <DropShadowEffect ShadowDepth="0" BlurRadius="5" />
            </Border.Effect>
        </Border>
        <Grid>
            <!--网格背景-->
            <Canvas x:Name="myCanvas" />

            <!--#region 遮罩阴影-->
            <Border x:Name="bor" Background="Gray" Width="800" Height="800" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="100,100,0,0" Opacity="0.3">
                <Border.Effect>
                    <BlurEffect Radius="10" KernelType="Box" />
                </Border.Effect>
            </Border>
            <!--#endregion-->

            <Canvas x:Name="box" Background="Transparent" MouseLeftButtonDown="Canvas_MouseLeftButtonDown" MouseLeftButtonUp="Canvas_MouseLeftButtonUp" MouseMove="Canvas_MouseMove">

                <!--#region 地图-->
                <Path Stroke="Orange" StrokeThickness="2" x:Name="rect" ClipToBounds="True">
                    <Path.Effect>
                        <DropShadowEffect ShadowDepth="0" BlurRadius="5" Opacity="0.7" Color="#FFFF8A16" />
                    </Path.Effect>
                    <Path.Data>
                        <GeometryGroup x:Name="geo" FillRule="EvenOdd">
                            <!--<RectangleGeometry Rect="100,100,800,800" />-->
                            <!--<PathGeometry Figures="M 150,100 L 100,100 L 100,900 L 800,900 L 800,920 M 850,920 L 850,900 L 900,900 L 900,100 L 100,100" />-->
                        </GeometryGroup>
                    </Path.Data>
                </Path>
                <!--#endregion-->

                <!--#region 小球-->
                <Ellipse x:Name="elp" Width="20" Height="20" Fill="Orange" Canvas.Left="110" Canvas.Top="110">
                    <Ellipse.Effect>
                        <DropShadowEffect ShadowDepth="0" BlurRadius="6" Color="#FFD88D04" />
                    </Ellipse.Effect>
                </Ellipse>
                <!--#endregion-->

            </Canvas>


        </Grid>

        <!--#region 控制按钮组-->
        <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Margin="100,30" Orientation="Horizontal">
            <StackPanel Orientation="Horizontal" x:Name="sp">
                <CheckBox Content="编辑模式" x:Name="rcb" FontSize="12" Margin="0,0,10,0" Checked="CheckBox_Edit_Checked" />
                <CheckBox Content="Play模式" x:Name="pcb" FontSize="12" Margin="0,0,10,0"  Checked="CheckBox_Play_Checked" />
            </StackPanel>
            <Button Height="30" Width="80" Content="保存地图" Margin="10,0" Style="{StaticResource ButtonDashed}" Click="Save_Click" />
            <Button Height="30" Width="80" Content="另存为" Margin="10,0" Style="{StaticResource ButtonDashed}" Click="SaveAs_Click" />
            <Button Height="30" Width="80" Content="加载地图" Margin="10,0" Style="{StaticResource ButtonDashed}" Click="LoadMap_Click" />
            <Button Height="30" Width="80" Content="创建地图" Margin="10,0" Style="{StaticResource ButtonDashed}" Click="Create_Click" />
            <Button Height="30" Width="80" Content="撤销" Margin="10,0" Style="{StaticResource ButtonDanger}" Click="Undo_Click" />
            <Button Height="30" Width="80" Content="关闭" Margin="10,0" Style="{StaticResource ButtonDanger}" Click="CloseWin_Click" />
        </StackPanel>
        <!--#endregion-->

    </Grid>
</Window>
2.定义好主要的属性及枚举
        /// <summary>
        /// 小球可视范围区域
        /// </summary>
        EllipseGeometry egy = null;
        /// <summary>
        /// 当前模式(play&Redit)
        /// </summary>
        private static Pattern currentPattern;
        /// <summary>
        /// 键盘每一步操作移动的距离,单位px
        /// </summary>
        private double distance = 3;
        /// <summary>
        /// 绘制的直线
        /// </summary>
        private Line line;
        /// <summary>
        /// 绘制的辅助线
        /// </summary>
        private Line guide_Line;
        /// <summary>
        /// 迷宫地图实线的集合
        /// </summary>
        private List<LineGeometry> geometries = new List<LineGeometry>();
        /// <summary>
        /// 点击的初始位置
        /// </summary>
        Point orginPoint;
        /// <summary>
        /// 是否按下鼠标
        /// </summary>
        bool isPressed = false;
        /// <summary>
        /// 焦点处的小圆
        /// </summary>
        Ellipse ellipse;

        /// <summary>
        /// 地图存储文件夹路径
        /// </summary>
        private string MapFolderPath = UniTools.AppRootPath + "map";
        /// <summary>
        /// 当前打开的迷宫文件地址
        /// </summary>
        private string currentMazeFilePath = "";
        /// <summary>
        /// 模式
        /// </summary>
        public enum Pattern
        {
            /// <summary>
            /// 编辑模式
            /// </summary>
            Redit,
            /// <summary>
            /// 游戏模式
            /// </summary>
            Play
        }
3.绘制网格背景
#region 网格背景
        /// <summary>
        /// 绘制网格背景
        /// </summary>
        /// <param name="canvas"></param>
        public static void Draw(Canvas canvas)
        {
            var gridBrush = new SolidColorBrush { Color = Colors.Gray };

            double scaleX = 20;
            double currentPosY = 0;
            currentPosY += scaleX;
            while (currentPosY < canvas.ActualHeight)
            {
                Line line = new Line
                {
                    X1 = 0,
                    Y1 = currentPosY,
                    X2 = canvas.ActualWidth,
                    Y2 = currentPosY,
                    Stroke = gridBrush,
                    StrokeThickness = 0.1
                };
                canvas.Children.Add(line);

                currentPosY += scaleX;
            }

            double scaleY = 20;
            double currentPosX = 0;
            currentPosX += scaleY;
            while (currentPosX < canvas.ActualWidth)
            {
                Line line = new Line
                {
                    X1 = currentPosX,
                    Y1 = 0,
                    X2 = currentPosX,
                    Y2 = canvas.ActualHeight,
                    Stroke = gridBrush,
                    StrokeThickness = 0.1
                };
                canvas.Children.Add(line);

                currentPosX += scaleY;
            }
        }

        private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Draw(myCanvas);
        }

        #endregion
4.迷宫地图的创建

这里只要在相关事件里去初始化地图就行,清除掉绘制的线,还原小球的位置,隐藏掉遮罩层

        /// <summary>
        /// 初始化地图
        /// </summary>
        private void InitailzeBox()
        {
            bor.Visibility = Visibility.Collapsed;
            egy = null;
            box.Clip = null;
            geo.Children.Clear();
            box.Background = new SolidColorBrush(Colors.Transparent);
            var pathgeo = (PathGeometry)this.Resources["pg"];
            geo.Children.Add(pathgeo);
            geometries.Clear();
            box.Children.RemoveRange(2, box.Children.Count - 1);
            rcb.IsChecked = true;

            BackOrginLocation();
        }


        /// <summary>
        /// 初始化小球及圆形可视区域中心位置
        /// </summary>
        private void BackOrginLocation()
        {
            Canvas.SetLeft(elp, 110);
            Canvas.SetTop(elp, 110);

            if (egy != null)
            {
                egy.Center = new Point(110 + elp.Width / 2, 110 + elp.Height / 2);
            }
        }
5.编辑模式下的迷宫地图绘制

编辑模式下可以用鼠标在矩形区域内进行地图绘制,当鼠标处于某个焦点时按下会显示一个圆形的标志,然后拖拽鼠标,此时会有一条虚线(辅助线)跟随,到达指定位置抬起鼠标后即可完成一条线段的绘制,如此反复即可完成地图的绘制

       #region 鼠标操作相关事件处理
        private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (currentPattern == Pattern.Play) return;
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                isPressed = true;
                orginPoint = e.GetPosition(box);

                var result = VisualTreeHelper.HitTest(rect, orginPoint);
                if (result != null)
                {
                    CreateFocusPoint(orginPoint.X, orginPoint.Y);
                }
            }
            e.Handled = true;
        }
        private void Canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (currentPattern == Pattern.Play) return;
            if (isPressed)
            {
                isPressed = false;
                //移除焦点区域的圆
                RemoveFoucusPoint();
                //创建迷宫实线
                CreateMazeLiens();
                //释放鼠标捕获
                box.ReleaseMouseCapture();
            }
            e.Handled = true;
        }
        private void Canvas_MouseMove(object sender, MouseEventArgs e)
        {
            if (currentPattern == Pattern.Play) return;
            if (isPressed)
            {
                //终点坐标
                var EndPoint = e.GetPosition(rect);


                //创建一条实线
                if (line == null)
                {
                    line = new Line();

                    line.Stroke = Brushes.Orange;
                    line.StrokeThickness = 2;
                    //line.StrokeDashArray = new DoubleCollection() { 2, 3 };
                    line.StrokeDashCap = PenLineCap.Triangle;
                    line.StrokeEndLineCap = PenLineCap.Square;
                    line.StrokeStartLineCap = PenLineCap.Round;
                }

                if (!box.Children.Contains(line))
                {
                    box.Children.Add(line);
                }

                //创建辅助线
                CreateGuidesByLineAndEndPoint(EndPoint);

            }
            else
            {
                //移除实线和辅助线
                RemoveMazeLineAndGuides();
            }


            e.Handled = true;
        }
        #endregion
6.编辑模式和play模式的切换

play模式下需要显示遮罩层,并创建一个圆形的可视范围,可视范围会跟随小球运动,从而达到隐匿大部分地图的目的,增强迷宫的可玩性

     #region 编辑模式和play模式切换处理



        /// <summary>
        /// 切换操作模式
        /// </summary>
        private void SwitchPattern()
        {
            //Play模式
            if (currentPattern == Pattern.Play)
            {
                bool isSuccess = false;
                //自动保存当前已编辑内容
                Save(ref isSuccess);
                //重新加载地图
                LoadMapFromFolder();

                //1.显示遮罩层
                bor.Visibility = Visibility.Visible;
                //2.创建圆形可视范围
                if (egy == null)
                {
                    egy = new EllipseGeometry()
                    {
                        Center = new Point(130, 130),
                        RadiusX = 60,
                        RadiusY = 60
                    };
                }
                //3.区域裁剪(通过裁剪显示圆形可视范围内的元素)
                box.Clip = egy;
                box.Background = new SolidColorBrush(Colors.White);

            }
            else
            {
                //编辑模式

                //1.隐藏遮罩层
                bor.Visibility = Visibility.Collapsed;
                //2.圆形可视范围清空掉
                egy = null;
                //3.裁剪区域清空
                box.Clip = null;
                box.Background = new SolidColorBrush(Colors.Transparent);
            }
            //初始化可视范围位置
            BackOrginLocation();
        }




        private void CheckBox_Checked(object sender, RoutedEventArgs e)
        {
            if (!this.IsLoaded) return;

            CheckBox _ckb = sender as CheckBox;
            var name = _ckb.Name;

            foreach (CheckBox ckb in sp.Children)
            {
                if (ckb.IsChecked == true)
                {
                    ckb.IsChecked = false;
                }
            }


            if (name == "rcb")
            {
                //编辑模式
                currentPattern = Pattern.Redit;
                pcb.IsChecked = false;
            }
            else
            {
                //Play模式
                currentPattern = Pattern.Play;
                rcb.IsChecked = false;
            }

            //模式切换
            SwitchPattern();
        }





        #endregion
7.play模式下的操作及闯关成功判定

支持“WASD”键及小键盘的方向键控制小球的移动

        #region 键盘操作
        private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
        {

            var left = Canvas.GetLeft(elp);
            var top = Canvas.GetTop(elp);
            var val = 0d;
            var EndPosition = new Point();
            var hitPoint = new Point();


            switch (e.Key)
            {
                case Key.Up:
                case Key.W:
                    val = top - distance;
                    EndPosition.X = left;
                    EndPosition.Y = val;

                    hitPoint.X = EndPosition.X + elp.Width / 2;
                    hitPoint.Y = EndPosition.Y;
                    break;
                case Key.Down:
                case Key.S:
                    val = top + distance;
                    EndPosition.X = left;
                    EndPosition.Y = val;
                    hitPoint.X = EndPosition.X + elp.Width / 2;
                    hitPoint.Y = EndPosition.Y + elp.Height;
                    break;
                case Key.Left:
                case Key.A:
                    val = left - distance;
                    EndPosition.X = val;
                    EndPosition.Y = top;
                    hitPoint.X = EndPosition.X;
                    hitPoint.Y = EndPosition.Y + elp.Height / 2;
                    break;
                case Key.Right:
                case Key.D:
                    val = left + distance;
                    EndPosition.X = val;
                    EndPosition.Y = top;
                    hitPoint.X = EndPosition.X + elp.Width;
                    hitPoint.Y = EndPosition.Y + elp.Height / 2;
                    break;
                default:
                    return;
            }


            var _center_x = EndPosition.X + elp.Width / 2;
            var _center_y = EndPosition.Y + elp.Height / 2;

            //修改小球可视化范围位置
            if (egy != null)
            {
                egy.Center = new Point(_center_x, _center_y);
            }


            //通过命中测试判断是否碰到了实线
            bool isContinue = true;
            VisualTreeHelper.HitTest(rect, null, f =>
            {
                isContinue = false;
                return HitTestResultBehavior.Continue;
            }, new GeometryHitTestParameters(new EllipseGeometry(new Point(_center_x, _center_y), 10, 10)));

            //如果没有碰到实线则进行移动处理
            if (isContinue)
            {
                Canvas.SetLeft(elp, EndPosition.X);
                Canvas.SetTop(elp, EndPosition.Y);
            }

            //判断是否闯关成功
            if (EndPosition.Y > 900)
            {
                //成功
                MessageBox.Show("恭喜你闯关成功!");
                //初始化位置
                BackOrginLocation();
            }



        }

        #endregion
8.迷宫地图的保存

将绘制的线段集合对象序列化到xml文件中

        /// <summary>
        /// 保存地图
        /// </summary>
        /// <param name="isSuccess"></param>
        private void Save(ref bool isSuccess)
        {
            if (geometries != null && geometries.Count > 0)
            {
                //将绘制的线段填充到地图线段集合
                MazePath mp = new MazePath();
                mp.LineList = new List<MazePath.MazeLine>();
                foreach (var g in geometries)
                {
                    MazePath.MazeLine mline = new MazePath.MazeLine
                    {
                        StartPoint = g.StartPoint,
                        EndPoint = g.EndPoint
                    };
                    mp.LineList.Add(mline);
                }
                //序列化成xml文件
                UniTools.Serialize_to_xml(currentMazeFilePath, mp);

                if (System.IO.File.Exists(currentMazeFilePath))
                {
                    isSuccess = true;
                }
                else
                {
                    isSuccess = false;
                }
            }


        }
9.迷宫地图的加载

反序列化xml文件,将序列化后的地图线段(LineGeometry)填充到画板中并赋予相关的事件

        /// <summary>
        /// 根据文件路径加载地图
        /// </summary>
        /// <param name="path"></param>
        private void LoadMap(string path)
        {
            //初始化UI
            InitailzeBox();
            var _mp = (MazePath)UniTools.Deserialize_from_xml(path, typeof(MazePath));

            currentMazeFilePath = path;

            if (_mp != null)
            {
                if (_mp.LineList != null && _mp.LineList.Count > 0)
                {
                    foreach (var item in _mp.LineList)
                    {
                        LineGeometry lineGeometry = new LineGeometry
                        {
                            StartPoint = item.StartPoint,
                            EndPoint = item.EndPoint
                        };

                        geometries.Add(lineGeometry);

                        Path p = new Path()
                        {
                            Stroke = new SolidColorBrush(Colors.Orange),
                            StrokeThickness = 2,
                            Data = lineGeometry
                        };

                        p.MouseEnter += (s, m) =>
                        {
                            p.Stroke = new SolidColorBrush(Colors.Red);
                            p.StrokeThickness = 4;
                        };


                        p.MouseLeave += (s, l) =>
                        {
                            p.Stroke = new SolidColorBrush(Colors.Orange);
                            p.StrokeThickness = 2;
                        };


                        p.MouseLeftButtonDown += (s, b) =>
                        {
                            if (b.ClickCount == 2)
                            {
                                line = null;
                                box.Children.Remove(p);
                                geometries.Remove(lineGeometry);
                            }
                            else
                            {

                                isPressed = true;
                                orginPoint = b.GetPosition(box);

                                var result = VisualTreeHelper.HitTest(p, orginPoint);
                                if (result != null)
                                {
                                    CreateFocusPoint(orginPoint.X, orginPoint.Y);
                                }





                            }




                        };
                        //需要将绘制的线段加入到GeometryGroup中去才能进行整体的命中测试
                        geo.Children.Add(lineGeometry);

                        box.Children.Add(p);

                    }
                }
            }
        }

        /// <summary>
        /// 从目录中读取地图数据
        /// </summary>
        private void LoadMapFromFolder()
        {
            UniTools.CreateFolder(MapFolderPath);
            var fileInfo = System.IO.Directory.GetFiles(MapFolderPath);
            if (fileInfo.Length > 0)
            {
                var loadPath = fileInfo[0];
                LoadMap(loadPath);
            }
        }
代码已经开源,地址:https://gitee.com/xie_xiaodong/demos-for-wpf/tree/master

相关文章

  • 好玩且可自定义的迷宫游戏!

    效果预览 技术需求 要做一个可以高度自定义的迷宫游戏 技术框架 基于.net 4.6 的技术框架基于wpf的客户端...

  • wind爸爸:5分钟设计迷宫游戏

    今天我们开始聊如何和孩子一起自己设计更好玩的迷宫游戏。 自己设计迷宫游戏我把它分为纸上迷宫、DIY迷宫两大类,下面...

  • 基于Java的迷宫老鼠游戏

    一、功能简介 迷宫老鼠系统包括以下功能: 自定义迷宫大小 使用图的深度遍历随机生成迷宫 用户使用鼠标绘制自定义迷宫...

  • 好玩的弹珠迷宫

    世界很大,我不能把世界带给孩子,可是我要把最好的给孩子。每次的区域活动都是孩子们最喜欢的,这周的益智区就带孩子玩了...

  • 好玩的迷宫天地

    为帮助幼儿提高观察能力,逻辑思维能力,我们新投放了一批好玩的益智游戏玩具,在经过教师的教研活动后,我们大体了解了玩...

  • 欢乐故事 ‖ 游乐场

    游乐场里有好多好玩的游戏,海洋球城堡,迷宫,碰碰车,沙子,蹦蹦床等还有很多小狐狸贝贝不知道的游戏。 贝贝跟着狐...

  • 我喜欢的一本书

    我最喜欢的书是熊出没大迷宫,因为我觉得迷宫书特别的好玩,里面有正方的迷宫 还有云彩的迷宫等各种各样的迷宫,种类特别...

  • 萌宝日记236之迷宫游戏

    2019年1月22日 星期二 晴 我们今天晚上的游戏是迷宫。我们的迷宫游戏和通常认为的迷宫游戏不太一样,我和妈妈用...

  • 迷宫游戏

    宽宽最近迷上了穿越迷宫,小手指在路线图上游走,神情专注严肃,经常喊:妈妈,快看 ,我找到路啦,然后指给我看,神情愉...

  • 迷宫游戏

    迷宫游戏可以培养孩子的注意力和大局观。迷宫有两种,一种以线的处理为目的,一种以思考为目的。无论哪一种都是以提高信息...

网友评论

    本文标题:好玩且可自定义的迷宫游戏!

    本文链接:https://www.haomeiwen.com/subject/fgksertx.html