练手WPF(三)——扫雷小游戏的简易实现(上)
程序员文章站
2022-04-14 18:47:07
一、创建项目1.创建WPF项目,设置初始化窗口大小(初级难度):高x宽为430x350。2.添加文件夹Images,并添加相关图片。 3.xaml中引入图片资源。 4.添加窗口元素(1)菜单 (2)在菜单之后,之前添加其他界面元素 其中两个Image用于显示时钟和地雷数图例, ......
一、创建项目
1.创建wpf项目,设置初始化窗口大小(初级难度):高x宽为430x350。
2.添加文件夹images,并添加相关图片。
3.xaml中引入图片资源。
<window.resources> <bitmapimage x:key="imgspace" urisource="/images/space.bmp"/> <bitmapimage x:key="imgmine" urisource="/images/mine.png"/> <bitmapimage x:key="imgnum1_8" urisource="/images/num1_8.png"/> <bitmapimage x:key="imgforeground" urisource="/images/foreimg.png"/> <bitmapimage x:key="imgmineortimer" urisource="/images/mineandtimer.png"/> <bitmapimage x:key="imgbomb" urisource="/images/bomb.png"/> </window.resources>
4.添加窗口元素
(1)菜单
<dockpanel> <menu dockpanel.dock="top"> <menuitem header="游戏(_g)"> <menuitem x:name="menugamestart" header="开始游戏(_s)" click="menugamestart_click"/> <separator/> <menuitem x:name="menugameexit" header="退出游戏(_x)" click="menugameexit_click"/> </menuitem> <menuitem header="级别(_l)"> <menuitem header="初级(_l)" x:name="menulowlevel" ischecked="true" click="menulowlevel_click"/> <menuitem header="中级(_m)" x:name="menumiddlelevel" ischecked="false" click="menumiddlelevel_click"/> <menuitem header="高级(_h)" x:name="menuhighlevel" ischecked="false" click="menuhighlevel_click"/> </menuitem> <menuitem header="选项(_o)"> <menuitem x:name="menuoptionsmusic" header="音乐音效(_s)" ischeckable="true" ischecked="true" click="menuoptionsmusic_click"/> <separator/> <menuitem x:name="menushowallmine" header="显示地雷(_o)" click="menushowallmine_click"/> <menuitem x:name="menurestoremap" header="继续游戏(_f)" click="menurestoremap_click"/> <separator /> <menuitem x:name="menuresetdata" header="重置记录(_r)" click="menuresetdata_click" /> </menuitem> <menuitem header="帮助(_h)"> <menuitem x:name="menuhelpabout" header="关于(_a)..." click="menuhelpabout_click"/> </menuitem> </menu> </dockpanel>
(2)在菜单之后,</dockpanel>之前添加其他界面元素
<grid margin="3" dockpanel.dock="top"> <grid.rowdefinitions> <rowdefinition height="40"/> <rowdefinition height="*"/> </grid.rowdefinitions> <stackpanel grid.row="0" background="skyblue" verticalalignment="center"> <grid margin="2 2 2 2"> <grid.columndefinitions> <columndefinition width="*"/> <columndefinition width="*"/> </grid.columndefinitions> <stackpanel grid.column="0" horizontalalignment="right" margin="0 0 60 0" orientation="horizontal"> <image x:name="imgclock" width="35" height="35" /> <border height="30" width="60" cornerradius="6" borderthickness="1" verticalalignment="center" background="#333333"> <textblock x:name="textblocktime" text="" verticalalignment="center" horizontalalignment="center" fontsize="14" foreground="aliceblue"/> </border> </stackpanel> <stackpanel grid.column="1" horizontalalignment="left" orientation="horizontal" margin="40 0 0 0"> <image x:name="imgminenum" width="35" height="35" /> <border height="30" width="60" cornerradius="6" borderthickness="1" verticalalignment="center" background="#333333"> <textblock x:name="textblockminenum" text="" verticalalignment="center" horizontalalignment="center" fontsize="14" foreground="aliceblue"/> </border> </stackpanel> </grid> </stackpanel> <stackpanel grid.row="1" horizontalalignment="left" verticalalignment="top"> <canvas x:name="backcanvas" width="315" height="315" background="lightcyan" margin="10" /> </stackpanel> <stackpanel grid.row="1" horizontalalignment="left" verticalalignment="top"> <canvas x:name="forecanvas" width="315" height="315" margin="10" mouseleftbuttondown="forecanvas_mouseleftbuttondown" mouserightbuttondown="forecanvas_mouserightbuttondown"/> </stackpanel> </grid>
其中两个image用于显示时钟和地雷数图例,其后两个textblock分别用于显示读秒数和剩余地雷数。
两个重叠的canvas,分别显示底层图块和前景图块。底层图块游戏开始后是固定的,其中会显示随机分布的地雷及其周边地雷数值,而前景图块可以通过鼠标点击进行移除或标记为问号或小红旗。
二、载入图片资源
1、添加一个静态图片帮助类imagehelper,方便以后的图片切片操作。
public static class imagehelper { /// <summary> /// bitmapsource图片源剪切操作函数 /// </summary> /// <param name="bmpsource">等待剪切的源</param> /// <param name="cut">剪切矩形</param> /// <returns>已剪切的图片源</returns> public static bitmapsource cutimage(bitmapsource bmpsource, int32rect cut) { return new croppedbitmap(bmpsource, cut); } /// <summary> /// 将bitmapimage转换为bitmapsource /// </summary> /// <param name="bitmapimage">待转换的bitmapimage</param> /// <returns>bitmapsource</returns> public static bitmapsource bitmapimagetobitmapsource(bitmapimage bitmapimage) { imagesource imagesource = bitmapimage; bitmap bitmap = imagesourcetobitmap(imagesource); bitmapsource bitmapsource = bitmaptobitmapimage(bitmap);
bitmap.dispose(); return bitmapsource; } /// <summary> /// 将imagesource转为bitmap /// </summary> /// <param name="imagesource"></param> /// <returns></returns> public static system.drawing.bitmap imagesourcetobitmap(imagesource imagesource) { bitmapsource m = (bitmapsource)imagesource; bitmap bmp = new bitmap(m.pixelwidth, m.pixelheight, system.drawing.imaging.pixelformat.format32bpppargb); system.drawing.imaging.bitmapdata data = bmp.lockbits(new rectangle(system.drawing.point.empty, bmp.size), system.drawing.imaging.imagelockmode.writeonly, system.drawing.imaging.pixelformat.format32bpppargb); m.copypixels(int32rect.empty, data.scan0, data.height * data.stride, data.stride); bmp.unlockbits(data); return bmp; } /// <summary> /// 将bitmap转为bitmapimage /// </summary> /// <param name="bitmap"></param> /// <returns></returns> public static bitmapimage bitmaptobitmapimage(bitmap bitmap) { using (memorystream stream = new memorystream()) { bitmap.save(stream, system.drawing.imaging.imageformat.png); stream.position = 0; bitmapimage result = new bitmapimage(); result.begininit(); result.cacheoption = bitmapcacheoption.onload; result.streamsource = stream; result.endinit(); result.freeze(); return result; } } }
2、添加几个字段变量
主程序mainwindow.xaml.cs中添加:
private bitmapsource _bmpspace, _bmpmine, _bmpnum1_8, _bmpforeground, _bmpmineortimer, _bmpbomb; private system.drawing.size _cellsize = new system.drawing.size(35, 35);
3、获取图片资源
private void getimageresource() { bitmapimage bmpspace = (bitmapimage)tryfindresource("imgspace"); bitmapimage bmpmine = (bitmapimage)tryfindresource("imgmine"); bitmapimage bmpnum1_8 = (bitmapimage)tryfindresource("imgnum1_8"); bitmapimage bmpforeground = (bitmapimage)tryfindresource("imgforeground"); bitmapimage bmpmineortimer = (bitmapimage)tryfindresource("imgmineortimer"); bitmapimage bmpbomb = (bitmapimage)tryfindresource("imgbomb"); _bmpspace = imagehelper.bitmapimagetobitmapsource(bmpspace); _bmpmine = imagehelper.bitmapimagetobitmapsource(bmpmine); _bmpnum1_8 = imagehelper.bitmapimagetobitmapsource(bmpnum1_8); _bmpforeground = imagehelper.bitmapimagetobitmapsource(bmpforeground); _bmpmineortimer = imagehelper.bitmapimagetobitmapsource(bmpmineortimer); _bmpbomb = imagehelper.bitmapimagetobitmapsource(bmpbomb); }
4、将界面中用到的时钟和地图数这两张图片设置到位。
private void settimerandmineimage() { imgclock.source = imagehelper.cutimage(_bmpmineortimer, new int32rect(0, 0, _cellsize.width, _cellsize.height)); imgminenum.source = imagehelper.cutimage(_bmpmineortimer, new int32rect(1 * _cellsize.width, 0, _cellsize.width, _cellsize.height)); }
5、将上述两个方法添加到构造方法中。
运行程序,如图1。
三、设置状态枚举
添加一个myenum.cs类文件,让主cs文件看起来简练一点。内容如下:
// 游戏级别 public enum level { simple, normal, hard }; // 前景状态 public enum forestate { none, // 无 normal, // 正常覆盖 flag, // 红旗 question // 问号 }; // 底层状态 public enum backstate { mine = -1, // 地雷 blank = 0, // 空 }; // 游戏状态 public enum gamestate { none, // 未开始 stop, // 已停止 start, // 已开始 pause // 已暂停 };
四、添加gamelevel类,定义游戏级别相关参数
public class gamelevel { public int _minenum { get; set; } // 地雷数 public int _rowgrid { get; set; } // 横格子数 public int _colgrid { get; set; } // 纵格子数 public gamelevel(int currlevel, int minenum, int rowgrid, int colgrid) { _minenum = minenum; _rowgrid = rowgrid; _colgrid = colgrid; } }
五、定义主游戏数据变量
private int[,] _backdata = null; // 底部背景数据(雷-1,0为空白,数值(1-8)周围雷数) private int[,] _foredata = null; // 前景数据(1:正常盖住;2:红旗;3:问号) private image[,] _backimage = null; // 底图图片数组 private image[,] _foreimage = null; // 上方图片数组 private gamestate _gamestate = gamestate.none; private random rnd = new random(); // 随机数 private gamelevel _gamelevel; private level _level = level.simple;
另外还用到几个计时器,看后面的注释
// 计时 private system.diagnostics.stopwatch _stopwatchgame = new system.diagnostics.stopwatch(); // 游戏过程读秒 private dispatchertimer _timersettimetext = new dispatchertimer(); // 更新读秒文本框 private dispatchertimer _timerwinanim = new dispatchertimer(); // 用于胜利时动画 private dispatchertimer _timerbomb = new dispatchertimer(); // 用于踩雷动画
将计时器初始化放在构造方法中
// 计时器 _timersettimetext.interval = new timespan(0, 0, 1); _timersettimetext.tick += _timersettimetext_tick; // 动画计时器 _timerwinanim.interval = new timespan(0, 0, 0, 0, 300); _timerwinanim.tick += _timerwinanim_tick; // 用户踩雷后的动画 _timerbomb.interval = new timespan(0, 0, 0, 0, 200); _timerbomb.tick += _timerbomb_tick;
六、初始化游戏状态
private void initialgamestate() { _timersettimetext.stop(); _timerwinanim.stop(); _stopwatchgame.reset(); _stopwatchgame.stop(); _gamestate = gamestate.none; menugamepauseorcontinue.header = "暂停(_p)"; } private void _timersettimetext_tick(object sender, eventargs e) { textblocktime.text = ((int)_stopwatchgame.elapsed.totalseconds).tostring(); } private void _timerwinanim_tick(object sender, eventargs e) { }
private void _timerbomb_tick(object sender, eventargs e)
{
}
后面动画计时事件方法以后再加入,这里先空着。
七、初始化游戏主数据
private void initgamedata(level level) { switch (level) { case level.simple: _gamelevel = new gamelevel(9, 9, 10); break; case level.normal: _gamelevel = new gamelevel(16, 16, 40); break; case level.hard: _gamelevel = new gamelevel(30, 16, 99); break; } // 设置窗口大小 this.width = _cellsize.width * _gamelevel._rowgrid + 40; this.height = _cellsize.height * _gamelevel._colgrid + 20 + 100; // 获取屏幕大小 double screenwidth = systemparameters.workarea.width; double screenheight = systemparameters.workarea.height; // 使窗口居中 this.left = (screenwidth - this.width) / 2; this.top = (screenheight - this.height) / 2; // 设置绘图控件大小 backcanvas.width = _gamelevel._colgrid * _cellsize.width; backcanvas.height = _gamelevel._rowgrid * _cellsize.height; forecanvas.width = backcanvas.width; forecanvas.height = backcanvas.height; // 初始化前景和背景数据 _backdata = new int[_gamelevel._colgrid, _gamelevel._rowgrid]; _foredata = new int[_gamelevel._colgrid, _gamelevel._rowgrid]; for (int y = 0; y<_gamelevel._colgrid; y++) { for (int x=0; x<_gamelevel._rowgrid; x++) { _backdata[y, x] = (int)backstate.blank; _foredata[y, x] = (int)forestate.normal; } } // 初始化前景和背景图片数组 _backimage = new image[_gamelevel._colgrid, _gamelevel._rowgrid]; _foreimage = new image[_gamelevel._colgrid, _gamelevel._rowgrid]; // 清理绘制区 backcanvas.children.clear(); forecanvas.children.clear(); }
上一篇: 聊聊webpack 4