详解从零开始---用C#制作扫雷游戏

 更新时间:2020年6月25日 11:19  点击:1460

学C#的原因其实挺简单的,因为一直对游戏挺感兴趣,查了下比较流行的游戏引擎Unity的主要开发语言是C#,所以就决定从C#入手,学学面向对象的编程方法。

以前基本都做的是嵌入式开发,做嵌入式久了,基本上只用C语言,C语言面向过程的特性在嵌入式编程这种资源极度受限的情况确实十分有利,但这种方式在面对大型软件的开发的时候就很难胜任了。编程的模式其实是一种思维习惯,习惯久了以后,想改变确实是一个艰难的过程···

说起C#,其实在大学的时候学过一个学期,说来惭愧那时候倒也没把它当一门面向对象的语言(其实是当时根本不知道面向对象是啥),感觉跟C语言也就一点语法差异,把所有的用法全部归为语法不同,说来也神奇,这种方法倒也能编程。最终学期结束的时候交上去一份用Winform开发的扫雷游戏结束了我的C#学习,在那之后就再也没碰过C#。

现在重拾C#,为了免除掉不必要的干扰,并没有直接在Unity上学习,而是仍然在VS中学习,但这次选择了比较新的WPF,而不是WInform,作为学习,第一个任务还是跟以前一样做一个扫雷游戏。

写在不怎么前面的前面:本文主要分享下程序分析过程,具体的实现方法不是本文重点,对实现有问题的朋友可以自行评论区留言索要源码或者提问^_^。

一、分析

1.游戏分析

那进入正题,应该如何完成这个游戏。忽略细枝末节的部分(如计时,显示剩余雷数,菜单栏等)不说,就单说这个游戏的主体:扫雷区。

在游戏没开始的时候,扫雷区放眼望去其实只有一个东西,那就是方块...

 

忽略光影效果不谈(是的,我又忽略了···),所有方块的颜色都一样,都响应相同的事件,那就是左键和右键。左键点开方块,右键给方块做个标记,认定为地雷。再继续分析,方块具有不同的种类。有的方块点开之后周围会有一大片方块一起打开。有的方块下面是地雷,点开就GameOver。还有方块下面是数字,代表着周围有多少个地雷。(果然,我又忽略了鼠标两个键同时按自动打开周围格子和第二次右键可以显示问号的功能···但其实之后会发现这个功能其实要增加也会很简单)。

所以,先来总结下扫雷游戏实现的核心:

  1. 方块会响应鼠标事件(左键按下,左键单击,右键按下,鼠标移入,鼠标移出)。
  2. 方块被点开后的效果有三种(炸弹,数字,空),其中为空的时候会自动展开周围所有的方块。
  3. 方块只能被打开一次,之后不再响应按键事件。
  4. 当插旗的方块数和地雷数相等,并且每个包含地雷的方块都被插了旗,则游戏胜利。
  5. 当包含地雷的方块被打开,则游戏失败。

2.实现技术分析

经过分析,是不是发现扫雷的的玩法其实很简单,实现的技术也不难,全是静态的没有动画的存在。

方块的表现很像一个只能按一次的按钮(事实上,在大学的时候我就是直接继承的按钮控件)。

但这一次为了能使用到更多C#相关的东西我使用了更加麻烦的自定义控件的方式。

方块有三种表现形式,为特殊性,但很显然也具有共性,所以在设计的时候,我把按钮共性抽离出来,设计成了一个抽象的基类Cube。方块有三种类型,但因为我懒,所以把其中的两种(空白和数字)合并为了NumCube类,包含地雷的为BombCube类,这两个类分别继承了Cube。

Cube的实现:

 

Cube类中拥有以下字段:

 

ImageSource cubeNormalPic
ImageSource cubeOnPic
ImageSource cubeDownPic
ImageSource cubeDisablePic
ImageSource cubeFlagPic

这5个字段是用来设置Cube在各个状态所显示的图片的(普通,鼠标进入,左键按下,失能,标记)

Bool isEnable
Bool isFlag

这两个字段就是标记Cube是否被使能和Flag

Image cubeImageHigh
Image cubeImageLow

这2个是两个image控件,作用是用来显示图片,之所以要2个图片是因为旗子图片被设计为一个叠加在Cube上的图片。

下面再来重点讲下下面2个东西:

displayCube
mouseEvent

在设计中,这是两个接口,分别用来处理鼠标事件和方块的展开。不同于直接在内部直接实现接口,将两个接口设计为Cube属性是为了能动态的修改这两个接口的实现方式,不至于每次修改都需要对Cube内的代码进行修改,且可以实现每个不同的Cube都使用不同的代码而不需要使用重写,这种方式在设计模式中也叫“策略模式”。

Cube只拥有一个方法,那就是Open,但这个方法其实也是有display接口代理实现。

public void Open()   
{    
 if (displayCube != null)    
 {
  displayCube.Open(this);    
 }     
}

displayCube.Open(this)之所以要把自身传入,是因为Open方法要用到Cube自己的参数和方法。

BombCube继承自Cube

 

只添加了一个字段:

ImageSource bombPic

用来存储地雷图片.

NumCube 继承自Cube

 

Int bombNum

用来记录方块周围有多少个BombCube,当其为0的时候,NumCube就是显示为空的方块。

添加了一个组件lable用来显示数字Text。

interface的实现

 

分别为每种Cube设计了一种接口的实现方式,使用这种方式,若后期需要改为动画显示,也只需要实现一个动画的接口,赋值给对应的Cube就可以了。

二、实现

控件继承:

Wpf进行控件继承的时候需要注意,被继承的控件不能有xaml。

在继承的时候,xaml中需要加入如下语句:

< myTypes:Cube x:Class="扫雷.UserControl.NumCube"

xmlns=" http:// schemas.microsoft.com/w infx/2006/xaml/presentation "

xmlns:x=" http:// schemas.microsoft.com/w infx/2006/xaml "

xmlns:mc=" http:// schemas.openxmlformats.org /markup-compatibility/2006 "

xmlns:d=" http:// schemas.microsoft.com/e xpression/blend/2008 "

mc:Ignorable="d"

xmlns:myTypes="clr-namespace:扫雷.UserControl"

d:DesignHeight="18" d:DesignWidth="18">

Cube 鼠标事件的实现:

鼠标事件主要是在各个事件中实现对Cube图片的变换,例如鼠标移出事件

public void MouseLeaveCube(object sender, MouseEventArgs e)   
{      
 BombCube bombCube = sender as BombCube;     
 if (bombCube.IsEnable)     
 {
  isClicking = false;
  bombCube.cubeImageLow.Source =
  bombCube.cubeNormalPic;     
 }   
}

关于地雷位置的生成算法实现:

游戏很重要的一个方面是,每次地雷的位置应该不同。很容易想到应该用随机数来产生地雷的位置。这就需要随机生成N个不相同的坐标。本程序的实现方法是创建一个list<int>,之后使用随机数在0-sizeX * sizeY - 1之间随机生成一个数,检查list中是否包含该数字,若不包含则添加进list,直到list拥有N个元素停止。

List<int> BombIndexList=new List<int>();
      
Random ran = new Random();
      
do
      
{
 int bombIndex = ran.Next(0,sizeX * sizeY - 1);
 if(!BombIndexList.Contains(bombIndex))
 {
  BombIndexList.Add(bombIndex);
 }
 else
 {
   continue;
 }     
} while (BombIndexList.Count < BombNum);
IndexList = BombIndexList;

之后根据生成的list来确定坐标上应该是NumCube还是BombCube

for (int y = 0; y < sizeY; y++)     
{
 for (int x = 0; x < sizeX;x++)
 {
  //cube属性设置
  if(bombIndexList.Exists((int temp) => temp == x + y * cubeX))
  {
   cubexMatrix[x, y] =bombCubeList[bombIndex++];
  }
  else
  {
   numCubeList[numIndex].Text ="";
   cubexMatrix[x, y] =numCubeList[numIndex++];
  }
  cubexMatrix[x, y].IsFlag =false;
  cubexMatrix[x, y].Margin =new Thickness(x * 18, y * 18, 0, 0);
  cubexMatrix[x, y].IsEnable = true;
  SetCubeBombNum(cubexMatrix,cubeX, cubeY);
  bombGrid.Children.Add(cubexMatrix[x, y]);         
 }     
}

如何让空白Cube打开以后会打开周围的Cube:

因为这种打开方式有点类似于递归,需要有传染性(即若打开的也是空白Cube,则其也应该打开周围的Cube),所以执行该事件的时候一定要具有周围Cube的信息(即能获取到周围的控件)。

获取周围的Cube的方法有两种:

1.保存Cube自身的位置,并获取所有Cube的位置

2.保存周围Cube的信息

我使用的是第二种方式,之前Cube类中的Cubelist就是用来保存周围Cube的信息的。通过CubeList找到周围Cube,并触发他们的左键单击事件。

public void MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
  NumCube numCube = sender as NumCube;
  if (numCube.IsEnable && numCube.IsFlag == false)
  {
    // 完成在控件上点击
    if (isClicking)
    {
      isClicking = false;
      numCube.IsEnable = false;
      if (numCube.BombNum != 0)
        numCube.Text = Convert.ToString(numCube.BombNum);
      else
      {
        foreach (Cube cubeTemp in numCube.CubeList)
        {
          MouseButtonEventArgs args = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left);
          args.RoutedEvent = Cube.MouseLeftButtonDownEvent;
          cubeTemp.RaiseEvent(args);
          args.RoutedEvent = Cube.MouseLeftButtonUpEvent;
          cubeTemp.RaiseEvent(args);
        }
      }
    }
  }
}

一些小技巧:

1.可以把一些图片的修改放在属性的set内,例如disable的图片。

public bool IsEnable
{
  get { return isEnable; }
  set 
  { 
    isEnable = value;
    if (isEnable)
    {
      if (cubeNormalPic != null)
        cubeImageLow.Source = cubeNormalPic;
    }
    else
    {
      if (cubeDisablePic != null)
        cubeImageLow.Source = cubeDisablePic;
    }
  }
}

2.Wpf创建控件较慢,为了提升(修改宽度长度或地雷数量之后)游戏开始速度,应该预先创建控件,并把控件放入list或者arr保存,按照需求取出。

到这扫雷游戏的制作就没什么难度技术上的难度的,只需要通过百度了解一些WPF常用的事件,控件,xalm相关的知识就能做出一个扫雷游戏啦。相关源码就不发在这了,需要的朋友可以评论中找我,这次游戏制作让我对面向对象的基本编程方法的了解有了一个很大的提升,下次应该就可以在Unity中做游戏啦 哈哈。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持猪先飞。

[!--infotagslink--]

相关文章

  • C#实现简单的登录界面

    我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望大家能够喜欢。...2020-06-25
  • 浅谈C# 字段和属性

    这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
  • C#中截取字符串的的基本方法详解

    这篇文章主要介绍了C#中截取字符串的的基本方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-03
  • C#实现简单的Http请求实例

    这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
  • C#连接SQL数据库和查询数据功能的操作技巧

    本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
  • C#中new的几种用法详解

    本文主要介绍了C#中new的几种用法,具有很好的参考价值,下面跟着小编一起来看下吧...2020-06-25
  • 使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序)

    这篇文章主要介绍了使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#开发Windows窗体应用程序的简单操作步骤

    这篇文章主要介绍了C#开发Windows窗体应用程序的简单操作步骤,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
  • C#从数据库读取图片并保存的两种方法

    这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
  • C#和JavaScript实现交互的方法

    最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • 轻松学习C#的基础入门

    轻松学习C#的基础入门,了解C#最基本的知识点,C#是一种简洁的,类型安全的一种完全面向对象的开发语言,是Microsoft专门基于.NET Framework平台开发的而量身定做的高级程序设计语言,需要的朋友可以参考下...2020-06-25
  • C#变量命名规则小结

    本文主要介绍了C#变量命名规则小结,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-09
  • C#绘制曲线图的方法

    这篇文章主要介绍了C#绘制曲线图的方法,以完整实例形式较为详细的分析了C#进行曲线绘制的具体步骤与相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • C# 中如何取绝对值函数

    本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
  • c#自带缓存使用方法 c#移除清理缓存

    这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
  • c#中(&&,||)与(&,|)的区别详解

    这篇文章主要介绍了c#中(&&,||)与(&,|)的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
  • 经典实例讲解C#递归算法

    这篇文章主要用实例讲解C#递归算法的概念以及用法,文中代码非常详细,帮助大家更好的参考和学习,感兴趣的朋友可以了解下...2020-06-25
  • C#学习笔记- 随机函数Random()的用法详解

    下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#中list用法实例

    这篇文章主要介绍了C#中list用法,结合实例形式分析了C#中list排序、运算、转换等常见操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25