1. 前言
博主是个乐于分享的人,一直以来也在总结、分享与大家的鼓励中提升自我,我一直认为文章反映着作者对于生活的态度,而真正优美的文章是在与读者交流。早前,博主发布了第一篇CSDN博客:基于MATLAB的拼图游戏设计,其中详细介绍了简单的MATLAB拼图游戏的设计过程。让我意外的是,该博文自发布以来受到不少博友的关注,同时有朋友发来私信说该文对初学者帮助较大并希望博主能写一篇介绍有GUI界面的拼图游戏的博文。看到这些博主深受感动,但苦于一直太忙拖了许久。想来如今必须得抽出点时间为大家写个带GUI界面的程序并讲述一下了。因为GUI界面版内容较多,这里限于篇幅分成了上、中、下连续三篇依次讲解。希望本文能给对MATLAB GUI界面设计或小游戏感兴趣的朋友有所启发。
2. 拼图游戏实现的功能
简单来说,我们要实现的功能有:
一、通过点击文件选择按钮,弹出一个图片文件选择对话框,可选择任意图像文件并将选中的文件绝对路径显示在一个文本框中,同时将选中的文件显示在坐标轴中。效果如下:
二、通过一个弹出式菜单实现对拼图游戏难度的选择,即选择拼图的阶数,对应的开始游戏后的拼图也应能够分成相应数目的拼图块。效果如下:
三、游戏开始前或进行中,可以对对应的拼图块标识相应的数字作为提示信息,提示的数字应能够适应相应的拼图阶数。效果如下:
四、无论何种阶数的拼图,点击“开始拼图”按钮后,在拼图区点击拼图块能够移动拼图并最终能够完成游戏,得到完整的拼好的拼图。效果如下:
3. 拼图游戏GUI界面设计
MATLAB为用户提供了强有力的工具——句柄图形,方便对图形的各个方面进行控制。很多时候我们需要一个可以反复使用的实用函数,这时图形界面作为交互方法就显得更有意义,其中利用GUIDE建立GUI是一种简单快捷的设计方式。特别是作为一款小游戏,一个优美简洁的界面将为其增色不少,这节介绍拼图游戏GUI的设计思路与界面制作过程。
作为这个小项目的开始,我们有必要先新建一个文件夹并命名为jigsaw_GUI用于保存接下来用到的全部文件。也可事先准备两张喜欢的图片放在文件夹下作为后面拼图使用。打开MATLAB并用其打开到刚刚新建的文件夹下,在命令行输入“guide”,在弹出的对话框中点击“确定”,进入GUI设计界面。
在GUI设计界面中,从左侧的GUI对象选择区依次拖动控件到中间的GUI布局区。每布置好一个控件,为了美观和方便需要重新设置一下控件名字及控件上显示的文字等属性。如拖动一个静态文本控件(带TXT字样的那个)到布局区,并双击该控件调出属性检查器,修改“FontName”字体属性为“隶书”、“FontSize”字体大小为16,、显示的字符串“String”设置为“自制简易拼图游戏”,“Tag”(对象名)属性设置为“text_title”。
后面的控件可通过类似的方式进行布置与修改,为了方便后面显示图片我们需要两个axes(坐标轴)控件,分别命名为axes_original、axes_jigsaw用于显示原始图片与进行拼图显示的图片。为了能够设置拼图的难易程度,我们添加一个弹出式菜单控件(popupMenu),并在“String”属性中添加多行字符串,每行字符串低代表一个弹出选项,如下:
我们要设计的拼图游戏的整体布局如下图所示:
通过如下的对象浏览器,你就能看到我所添加的控件层次及命名情况了,大家可以照着上面的属性将所有控件拖入布局区完成GUI界面的设计。为了方便后面编程,每个控件应该都起一个简单易识别的名字,大家也可适当调整使得界面更加美观。完成后可点击工具栏那个绿色的三角按钮运行一下看看效果。
完成界面设计后点击保存,这时会保存设计好的包含GUI界面的fig文件以及对应的m文件,在m文件中我们可以添加和修改控件的回调函数,以实现我们需要的效果。当然也可以在fig文件中右击控件,选择“查看回调”选择相应回调函数即会进入该函数的编辑界面。在接下来的文章中都会介绍如何为相应控件添加对应回调函数来实现我们需要的功能。
4. 图片选择功能实现
这里我们要实现一个简单的基础功能:当点击按钮时,弹出文件选择对话框,在对话框中我们可以选择一个图像文件,点击确定后其文件完整路径显示在旁边的文本框中,同时在前面提到的两个axes(坐标轴)中显示我们选中的图片。其效果便是如下图所示的那样:
我们还是回到之前的fig文件中,选中之前保存的文件右击“用guide打开”。打开GUI界面后选中用于文件选择的按钮,右击“查看回调”,选择“Callback”,就会转到Callback函数的编辑器中,如下图所示:
Callback函数是在创建图形界面的时候自动生成的,但只是为你列了一个框架没有实际的操作执行,因为我之前为这个按钮命名是“pushbutton_path”,所以这里生成的函数名是pushbutton_path_Callback(),具体看大家自己的命名。回调函数的声明为:function 函数名(hObject, eventdata, handles),hObject为发生事件的源对象(这里可以视为该按钮的句柄);handles为传入的GUI数据(可认为包含了所有控件的句柄,可通过handles访问到),在这个函数中就可以添加代码了。
查看MATLAB官方文档发现,文件选择对话框可通过uigetfile函数实现,其调用方式如下:
file = uigetfile 打开一个模态对话框,其中列出了当前文件夹中的文件。用户可以在这里选择或输入文件的名称。如果文件存在> 并且有效,当用户点击打开时,uigetfile 将返回文件名。如果用户点击取消或窗口关闭按钮 (X ),uigetfile 将返回 0。
java
[file,path] = uigetfile('*.png',... 'Select an icon file','icon.png') ——《MATLAB官方文档》
具体添加的完整代码如下:
java % --- 点击图片文件选择按钮(pushbutton_path)时执行. function pushbutton_path_Callback(hObject, eventdata, handles) global flag; % 全局变量声明,flag决定是否能点击拼图 % 弹出文件选择框,选择一张图片 [file,path] = uigetfile({'*.jpg;*.jpeg;*.png;*.bmp;*.tif',... '图片文件 (*.jpg,*.jpeg,*.png,*.bmp,*.tif)'},'选择一张图片'); if isequal(file,0) % 若文件不存在 set(handles.edit_path,'String','请选择一张图片'); else fileName= fullfile(path,file); % 选择的图片绝对路径 set(handles.edit_path,'String',fileName); % 显示选择的图片路径 pic_data=imread(fileName); % 判断拼图的阶数 n=get(handles.popupmenu_rank,'value'); rank_Tag=n+2; % 选取最短那条边作为边长 len=min([size(pic_data,1),size(pic_data,2)]); len_col=round(len/rank_Tag);% 每个拼图块长度 len_row=round(len/rank_Tag);% 每个拼图块宽度 % 将图片调整为正方形 pic_data=imresize(pic_data,[rank_Tag*len_col rank_Tag*len_row]); axes(handles.axes_original) % 确定要显示的坐标轴 image(pic_data);% 显示图片 axis off; % 关闭坐标显示 % 显示拼图 axes(handles.axes_jigsaw) image(pic_data); axis off; % 拼图块数字标识 Tag=[1:1:rank_Tag^2-1,0]; Tag=reshape(Tag,rank_Tag,rank_Tag); Tag=Tag'; % 根据checkbox的是否勾选决定是否显示拼图块数字提示 ismask=get(handles.checkbox_num,'Value'); axes(handles.axes_jigsaw); for i=1:size(Tag,1) for j=1:size(Tag,2) text(len_col/2*(2*j-1)-10,len_row/2*(2*i-1),num2str(Tag(i,j)),'FontSize',55-rank_Tag*5,'Color','c') end end if ~ismask % 若未勾选状态,删除所有text图形 h=findall(gca,'type','text'); delete(h); end flag=false;% 重选文件后置flag防止误点击 end
【代码解释】
代码第3行首先申明了一个全局变量flag,这是一个在后面的文件中也会用到的变量,flag的值决定了点击拼图块后是否对其作出反应,就先写在这边了后面会解释其具体功能;第6行弹出一个文件选择框,并限定文件格式为jpg, jpeg, png, bmp, tif类型;第9行判断是否选择了有效文件,若不存在,在第10行设置旁边的静态文本框显示字符串“请选择一张图片”作为提示;而当文件存在则执行第12-49行的代码。
第12-13行首先得到选择图片的绝对路径,并在路径显示的文本中显示出来;第14行读取图片文件。读取了文件后并不是直接显示,因为我们读入的图片可能尺寸各异,我们需要适当处理将其缩放为一个正方形。那边长该取多少合适呢?这里简单考虑,取图片长宽中最短的那个作为边长,直接利用imresize()函数将其缩放为正方形。第16行为获取当初我们设置的用来选择拼图阶数的弹出菜单控件的实际值,n为选择的那一项的序号(如第一项“3阶拼图”的序号为1),因此可以简单计算拼图的阶数rank_Tag为n+2(如选中第一项rank_Tag=3)。知道阶数那么每个拼图块的长宽就可由边长除以阶数得到,第20-21行计算每块拼图块的长宽。
得到了调整好的拼图,需要在坐标轴中将图片显示出来,第24-26行为在显示原图的坐标轴显示原始图片(未拆分打乱的图)并关闭坐标轴显示;同理,第28-30行是在拼图的主坐标轴中显示图片。
第31-47行其实是为了实现另一个功能:为每块拼图块显示提示的数字,这一功能在后面会介绍,这里为解释代码只简单说一下。第32-34行是得到标识拼图块的数字矩阵,因为当前还没有打乱拼图,如果选择了数字提示这时显示的一定是如1,2,3;4,5,6;7,8,0的顺序数字,所以这里直接产生一个顺序矩阵用于标注。第37行是获取那个用于是否显示提示数字的勾选框的值,值为1表示勾选,0表示未选。第38-43行在每块拼图块的中间位置添加text图形以显示对应数字。第44-47行判断勾选状态,若未选删除所有text对象(在图形上表现为无数字标识)。