截止于1.6版本,除了主角外,共有33名分别来自蒙德与璃月的角色,在我们玩游戏的过程中,每位角色都需要升级等级、天赋、圣遗物等等,对于一些喜欢全员升级的人来说,有一个小型免费的应用/程序来记录着所有角色的信息并能根据版本迭代更新增加角色,将会是一个很不错的选择。
下面这个是初具规格的“小程序”,根据自行设定的目标值来记录不同角色的等级、天赋、圣遗物是否达到预期的用matlab制作的GUI。
下面有这个“小程序”的资源,以及GUI构建的全过程解析。
下载程序的全部相关资源(对应原神v1.6),建议下载后学习下文
GUI构建目录
1 资源与控件
在上面展示的GUI界面中,存在许多个角色,如果仅仅用文字来展示不同的角色,可能不太好看。在该教程中,将使用不同的图片来展示角色头像。角色的图片,可以在原神官网获取,或者下载上方链接资源;另外在GUI交互过程中,需要记录保存信息,教程使用Excel作为储存。如图1所示是ys.xlsx的主要内容(部分),在目前的版本中C、D、H列暂时没用到。

从G列开始,到V列为止,默认的基础数据可参考图中第21行(魈),在后续的初始化中可将对应角色的值重置为此。
完成了初始数据和基本图片的部署后,需要打开matlab,在命令行窗口输入guide回车后新建一个空的GUI,并将该GUI存放于与数据和图片同一目录下。然后通过如下图(右)所示的GUI界面,按照图中所示方式摆放控件(或自行摆放),然后双击控件修改字体大小(FontSize)、显示的文字(String)、标签(Tag)等信息;该图中的红框部分是控件对应的Tag标签命名。
这里用到了列表框(listbox)、按钮(pushbutton)、坐标轴(axes)和静态文本(text),这些部分的回调函数会在下一章分别讲述。
2 逻辑整理
基本思路:(1)点击列表→显示头像和右边的控件(仅第一次);(2)点击获取→右边控件可编辑;(3)右边控件修改完成后点击保存→记录进Excel并检测哪些目标已完成;(4)点击清除→初始化右边控件的数据。
2.1 列表
在我们运行程序时,进入的界面应该是只有列表框和刷新列表的按钮,其它都是'visible'属性值为'off'的,在控件很多的时候应该用循环的方式设置。根据需求,在(比如我命名的是launcher)编辑器的launcher_OpeningFcn函数末尾加入两个函数:隐藏右边的控件、给列表赋值。
对控件的visible值做出批量修改
function inhert(e, value)
if ~strcmp(get(e.relics5, 'visible'), value) % 判断需要设置的值是否与原值不同
% fnc 是所有需要操作visible值的控件对应的tag标签,...是换行继续写
fnc = {...
'axes2', 'pushbutton1', 'save', 'gNow', 'gEx', ...
'gradeText', 'gradeDec', 'grade', 'gradeInc', 'gradeExDec', 'gradeEx', 'gradeExInc', ...
'funText', 'funDec', 'fun', 'funInc', ...
'aText', 'aDec', 'a', 'aInc', 'aExDec', 'aEx', 'aExInc', ...
'eText', 'eDec', 'e', 'eInc', 'eExDec', 'eEx', 'eExInc', ...
'qText', 'qDec', 'q', 'qInc', 'qExDec', 'qEx', 'qExInc', ...
'rText', 'relics1', 'relics2', 'relics3', 'relics4', 'relics5'
};
for i = 1:numel(fnc)
% a.b 可以写成 a.('b')
set(e.(fnc{i}), 'visible', value)
end
end
更新listbox
function relist(e)
[x, showstr1] = incode('A3:A35'); % 读取文本给showstr1,这里从图1看是角色名称
showstr2 = incode('G3:G35'); % 读取是否拥有角色 1|0
showstrL = numel(showstr2);
showstr{showstrL} = '';
for i = 1:showstrL % 没有的角色在名字前加'(x)'符号,如 (x)琴
if showstr2(i)
showstr{i} = showstr1{i};
else
showstr{i} = ['(x)', showstr1{i}];
end
end
% 在列表框展示所有角色名称,注意是cellstr()
set(e.listbox1, 'String', cellstr(showstr))
读取Excel数据
function [num, txt, raw] = incode(position)
% 返回的txt是cell
[num, txt, raw] = xlsread('ys.xlsx','Sheet1',position);
根据上面的自定义函数,可以得到launcher_OpeningFcn函数
function launcher_OpeningFcn(o, d, e, varargin)
e.output = o;
guidata(o, e);
% 打开launcher执行
inhert(e, 'off'); % 隐藏控件
relist(e); % 给列表框获取角色名称
2.2 显示角色数据
在打开launcher后可以得到角色列表,然后需要点击角色的时候显示控件和控件的内容,这个时候需要编辑的则是listbox1_CallBack函数
function listbox1_Callback(o, d, e)
inhert(e, 'on'); % 显示控件
index = get(e.listbox1, 'Value'); % 获得回调位置,返回一个数(1, 2, 3, ...)
% 显示右边的图
[x, filename] = incode('F3:F35');
img = imread(['roles/', filename{index}]);
axes(e.axes2), imshow(img)
% 更新右边的数据
renew(e, index)
% 更新已完成
gradeComplete(e);
funComplete(e);
aComplete(e);
eComplete(e);
qComplete(e);
relicsComplete(e);
更新右边数据的renew函数
function renew(e, index)
line = num2str(index+2);
data = incode(['G', line, ':V', line]);
area = {
'pushbutton1', '', 'fun', 'grade', 'a', 'e', 'q', ...
'relics1', 'relics2', 'relics3', 'relics4', 'relics5', ...
'aEx', 'eEx', 'qEx', 'gradeEx'
};
% 更新按钮
tag = {'获取', '清除'};
set(e.pushbutton1, 'String', tag{data(1)+1})
%更新右边控件数据
relics_txt = {'花', '羽', '沙', '杯', '冠'};
L = numel(area);
for i = 3:L
if i >= L-8 && i <= L-4 % 圣遗物部分(R)
if data(i)
set(e.(area{i}), 'String', relics_txt{i-L+9})
else
set(e.(area{i}), 'String', 'x')
end
else
set(e.(area{i}), 'String', num2str(data(i)))
end
end
检测是否达到预计值的Complete函数
function setCBG(o, t) % 设置静文本背景
switch(t)
case 1
set(o, 'BackgroundColor', 'green')
otherwise
set(o, 'BackgroundColor', '0.94,0.94,0.94')
end
function gradeComplete(e) % 等阶完成否
g = get(e.grade, 'string');
ge = get(e.gradeEx, 'string');
setCBG(e.gradeText, strcmp(g, ge) && ~strcmp(g, '0'))
function funComplete(e) % 好感完成否
setCBG(e.funText, strcmp(get(e.fun, 'string'), '10'))
function aComplete(e) % A完成否
a = get(e.a, 'string');
ae = get(e.aEx, 'string');
setCBG(e.aText, strcmp(a, ae) && ~strcmp(a, '1'))
function eComplete(v) % E完成否
e = get(v.e, 'string');
ee = get(v.eEx, 'string');
setCBG(v.eText, strcmp(e, ee) && ~strcmp(e, '1'))
function qComplete(e) % Q完成否
q = get(e.q, 'string');
qe = get(e.qEx, 'string');
setCBG(e.qText, strcmp(q, qe) && ~strcmp(q, '1'))
function relicsComplete(e) % 圣遗物完成否
r1 = get(e.relics1, 'string');
r2 = get(e.relics2, 'string');
r3 = get(e.relics3, 'string');
r4 = get(e.relics4, 'string');
r5 = get(e.relics5, 'string');
setCBG(e.rText, strcmp([r1, r2, r3, r4, r5], '花羽沙杯冠'))
2.3 加减与点击逻辑判定
由于教程存在多组加减逻辑,这里以A技能的举例(前面某个图的aText, a, aEx等);圣遗物R中,以“花”为例。
function aDec_Callback(o, d, e) % A--
if strcmp(get(e.pushbutton1, 'string'), '清除') %判断是否拥有角色,没有不可编辑,下同
n = str2num(get(e.a, 'String'));
if n > 1 % 下限是1
set(e.a, 'String', n-1)
end
end
function aInc_Callback(o, d, e) % A++
if strcmp(get(e.pushbutton1, 'string'), '清除')
n = str2num(get(e.a, 'String'));
if n < 10 % 上限是10
set(e.a, 'String', n+1)
if str2num(get(e.aEx, 'String')) < n+1 % 如果期望值小于当前值
set(e.aEx, 'String', n+1) % 跟着+1
end
end
end
function aExDec_Callback(o, d, e) % AEx--
if strcmp(get(e.pushbutton1, 'string'), '清除')
n = str2num(get(e.aEx, 'String'));
if n > str2num(get(e.a, 'string')) % 下限不低于当前值
set(e.aEx, 'String', n-1)
end
end
function aExInc_Callback(o, d, e) % AEx++
if strcmp(get(e.pushbutton1, 'string'), '清除')
n = str2num(get(e.aEx, 'String'));
if n < 10 % 上限10
set(e.aEx, 'String', n+1)
end
end
function relics1_Callback(o, d, e) % 花
if get(e.relics1, 'string') == 'x'
set(e.relics1, 'string', '花')
else
set(e.relics1, 'string', 'x')
end
% 或者写成
% set(e.relics1, 'string', {'花', 'x'}{get(e.relics1, 'string') == 'x'})
2.4 保存与清除
为了保证GUI的运行速度,没有在按钮中加入保存的功能,而是单独设置了一个保存的按钮,完成修改后点击保存,保存的同时也要更新一下哪些已经达到目标值。
function save_Callback(o, d, e)
if strcmp(get(e.pushbutton1, 'string'), '清除') %拥有角色才能保存
used = {'fun', 'grade', 'a', 'e', 'q', 'relics1', 'relics2', 'relics3', 'relics4', 'relics5', 'aEx', 'eEx', 'qEx', 'gradeEx'};
N = numel(used);
data(N) = 0;
for i = 1:N
if i >= N-8 && i <= N-4
if get(e.(used{i}), 'string') == 'x' %检查圣遗物拥有状态
data(i) = 0;
else
data(i) = 1;
end
else
data(i) = str2num(get(e.(used{i}), 'string'));
end
end
line = num2str(2 + get(e.listbox1, 'value')); %得到当前角色在Excel的行
recode(['I', line, ':V', line], data); %写入到Excel
% 刷新完成度
gradeComplete(e);
funComplete(e);
aComplete(e);
eComplete(e);
qComplete(e);
relicsComplete(e);
end
function recode(position, value) %写入数据
xlswrite('ys.xlsx', value, 'Sheet1', position);
有获取就有清除(虽然听起来好鸡肋,但是为了更合理),获取与清除在一个角色下是同一个按钮,需要读取按钮的状态来决定是什么操作。获取是写入文件,拥有角色;清除是初始化角色的数据并刷新。
function pushbutton1_Callback(o, d, e)
index = get(e.listbox1, 'value');
if strcmp(get(e.pushbutton1, 'string'), '清除')
% 清除角色数据
set(e.pushbutton1, 'String', '获取')
initMtx = [0,0,1,0,1,1,1,0,0,0,0,0,1,1,1,0];
recode(['G', num2str(2+index)], initMtx);
renew(e, index); %刷新右方数据
gradeComplete(e); %检测完成度/或者说是重置
funComplete(e);
aComplete(e);
eComplete(e);
qComplete(e);
relicsComplete(e);
else
recode(['G', num2str(2+index)], 1);
set(e.pushbutton1, 'String', '清除')
end
2.5 刷新列表
点击刷新列表后,更新列表框中的角色信息,只需要如2.1的更新listbox即可。
function refresh_Callback(o, d, e)
relist(e);