美文网首页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

    相关文章

      网友评论

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

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