用App Designer 制作2048小游戏
用App Designer制作的2048,MATLAB版本是2020b。记录下创作思路,以免日后忘记。
APP界面设计
APP界面如下,为了好玩,还加入了游戏进行时播放音乐的功能。
下面是游戏结束界面:
除了按钮和开关部分,其余都可用标签控件制作。游戏结束界面在制作时将其Visible属性设为Off(确保在主界面上层),当判定游戏结束时再将Visible属性修改为On即可。
app.gameOverLabel.Visible = 'on';
app.gameOverLabel2.Visible = 'on';
app.gameOverLabel3.Visible = 'on';
标签和按钮上的文字通过其Text属性修改
按钮上的图案可通过修改其Icon属性改变
规则设计
游戏每一步可以选择“上、下、左、右”中的一项操作,每项操作需关注三点,一是数字格向空白格的转移,二是相同数字格的合并相加,三是移动完成后会随机将一个空白格变为2。
我们不可能直接对方格进行操作,考虑将其与一个
4
×
4
4\times4
4×4矩阵对应起来,则数字格向空白格的转移可以这样做:
设有
2
×
3
2\times3
2×3矩阵
A
\bm{A}
A
[
1
0
2
3
4
5
]
\left[\begin{array}{l} 1 &0 &2 \\ 3 &4 &5 \end{array}\right]
[130425]
现要把第二行“向上推”,即要使数字“4”的位置由
(
2
,
2
)
(2,2)
(2,2)变为
(
1
,
2
)
(1,2)
(1,2),而
(
2
,
2
)
(2,2)
(2,2)在数字“4”被“推走”后变为0,矩阵
A
\bm{A}
A变为
A
′
\bm{A^{'}}
A′
[
1
4
2
3
0
5
]
\left[\begin{array}{l} 1 &4 &2 \\ 3 &0 &5 \\ \end{array}\right]
[134025]
设有一个
1
×
3
1\times3
1×3的临时矩阵
T
\bm{T}
T,
A
\bm{A}
A从第二行取出
T
\bm{T}
T加到第一行得到
A
′
\bm{A^{'}}
A′
A
′
=
A
−
(
0
T
)
+
(
T
0
)
\bm{A^{'}}= \bm{A}- \left(\begin{array}{l}\bm{0} \\ \bm{T}\end{array}\right) + \left(\begin{array}{l}\bm{T} \\ \bm{0}\end{array}\right)
A′=A−(0T)+(T0)
那么不难得到
T
=
[
0
4
0
]
\bm{T}=[0\quad4\quad0]
T=[040]
如何计算呢?
只需用
A
(
2
,
:
)
\bm{A}(2,:)
A(2,:)点乘
[
0
1
0
]
[0\quad1\quad0]
[010],
[
0
1
0
]
[0\quad1\quad0]
[010] 就是将
A
(
1
,
:
)
\bm{A}(1,:)
A(1,:)非零元素变为
0
0
0,零元素变为
1
1
1得到的。
T
=
A
(
2
,
:
)
⊙
∼
A
(
1
,
:
)
\bm{T} = \bm{A}(2,:)\odot \thicksim\bm{A}(1,:)
T=A(2,:)⊙∼A(1,:)
temp = ~app.theMatrix(4-i,:).*app.theMatrix(5-i,:);
app.theMatrix(4-i,:) = app.theMatrix(4-i,:) + temp;
app.theMatrix(5-i,:) = app.theMatrix(5-i,:) - temp;
相同数字格的合并相加也有类似的计算方法。
在空白格随机生成数字“2”怎么实现呢?
首先要确定空白格的位置,即矩阵中零元素的位置。在MATLAB中用find()函数来获取目标元素的索引。
zeroIndex = find(app.theMatrix == 0);
然后用randperm()生成一个随机整数,这个随机整数不能大于矩阵零元素的个数。我们利用这个随机整数来确定一个零元素的位置,然后以“2”替换该零元素。
当矩阵中没有零元素,并且相邻元素也各不相同时,应当判定游戏结束。
后端到前端
要把矩阵映射到游戏主界面,有两个需求:一是对应位置的数字显示,其中“0”不显示;二是不同数字对应着不同的背景颜色。这个只需要对每个方格的Text属性和BackGroundColor属性依次赋值就行,注意Text属性应当用字符串类型赋值,为此可以使用string()函数将数值矩阵转换为字符串矩阵。
strMatrix = string(app.theMatrix);
特别地,零元素应当转换成空字符串。
strZeroIndex = strMatrix == "0";
strMatrix(strZeroIndex) = "";
每进行一步操作都要将主界面更新一遍,在此更新中分数界面应当一并更新。
按钮回调
按前面所说的方式分别设计向上键、向下键、向左键和向右键的回调函数,包含两部分:一是对矩阵进行修改,二是将修改后的矩阵映射到主界面。点击按钮的任务用键盘也能完成,我们可以添加键盘回调,并在其中关联按钮回调。使用WindowKeyRelease将会按键释放时执行回调。
function UIFigureWindowKeyRelease(app, event)
key = event.Key;
switch(key)
case {"uparrow","w"}
app.buttonUpButtonPushed();
case {"downarrow","s"}
app.buttonDownButtonPushed();
case {"leftarrow","a"}
app.buttonLeftButtonPushed();
case {"rightarrow","d"}
app.buttonRightButtonPushed();
end
end
除了上下左右键,这里还设计了“重新游戏”和“清除”。“重新游戏”将会清零所得分,使游戏主界面恢复到初始状态。游戏中的最高分将会保存到文件中,下次打开APP时,程序会读取文件记录的最高分。使用“重新游戏”并不会清除最高分记录,而使用“清除”将会修改文件记录的最高分为0。
如果使用fread()和fwrite()读写文件,注意读写格式。若使用默认的读写格式,可能会因位数不够而无法读写较大的游戏分数。
f = fopen(app.dataPath,'r');
if f>0
app.bestScore = fread(f,'int32');
fclose(f);
end
运行方式
在我们运行程序后,首先APP界面及我们添加的控件会被加载出来,然后执行startupFcn()函数。在此之后,程序会等待我们触发回调,当回调对应的event发生时,执行回调。
以本项目为例,startupFcn()函数如下
function startupFcn(app)
app.loadMusic();
app.scoreStart();
app.nextStep();
app.nextStep();
end
首先,用loadMusic()函数加载音乐,之后我们可以用开关来控制音乐的暂停和播放。接下来用scoreStart()将得分清零,并从文件中读取最高分。然后使用nextStep()刷新界面,因为游戏初始有两个“2”,而nextStep()只能产生一个,所以使用两次。
关于批量创建控件
App Designer可以使用代码快速创建控件。如本例的16个数字方格,我们可以通过循环的方式快速创建控件并完成配置。
首先在properties中添加控件名,如
Num matlab.ui.control.Label
其中,Num是控件名,后面的 matlab.ui.control.Label 表明了Num是Label控件。之后,我们写一个创建控件的函数。
function createNum(app)
for i=1:4
for j=1:4
app.Num(i,j) = uilabel(app.UIFigure);
app.Num(i,j).BackgroundColor = [0.902 0.902 0.8];
app.Num(i,j).HorizontalAlignment = 'center';
app.Num(i,j).FontName = 'Calibri';
app.Num(i,j).FontSize = 40;
app.Num(i,j).Position = [-60+100*j 440-100*i 90 90];
app.Num(i,j).Text = '';
end
end
end
注意需要在properties中声明Num的类型才能对控件的属性进行点索引。
对应的工程文件包
2048小游戏——基于MATLAB App Designer
因为包含了音乐文件,所以比较大。