铁血丹心

 找回密码
 我要成为铁血侠客
搜索
查看: 1395|回复: 10

[lua复刻] 【新手向】lua复刻版0.4源码解析(二)通用菜单函数ShowMenu

[复制链接]
 楼主| 发表于 2022-11-20 00:04 | 显示全部楼层 |阅读模式

马上注册,结交更多侠友!

您需要 登录 才可以下载或查看,没有账号?我要成为铁血侠客

x
本帖最后由 决定吃小菜 于 2022-11-29 05:13 编辑

源码解析:


我们的目标是读懂每一行代码。



1.代码回顾
上期我们止步于标题按钮的文字修改,再回顾一下代码先:
  1. function StartMenu()
  2.     local menu = { { "故事模式", Normal_Game, 1 },
  3.                    { "挑战模式", Endless_Game, 1 },
  4.                    { "归隐山林", nil, 1 } };
  5.     local menux = (CC.ScreenW - 4 * CC.FontBig) / 2

  6.     local menuReturn = ShowMenu(menu, #menu, 0, menux, CC.ScreenH - 4 * CC.FontBig, 0, 0, 0, 0, CC.FontBig, C_STARTMENU, C_RED)

  7.     if menuReturn == 3 then
  8.         JY.Status = GAME_END;
  9.     end

  10. end
复制代码
  • menu变量记录了三个选项
  • menux变量只是获得了一堆复杂计算的结果,是按钮x轴的起始位置。
  • menuReturn变量记录我们今天的主角ShowMenu方法的结果

最后的if...end代码块让游戏状态变成了结束,于是游戏程序跳出循环,窗口关闭。


程序运行完毕后就会被操作系统回收资源,所以游戏都是运行在一个死循环中阻止程序运行完,只有需要关闭时才会主动跳出死循环

2.ShowMenu


如果你已经用上了Intellij IDEA社区版+EmmyLua插件,那么按住Ctrl+鼠标左键点击ShowMenu会自动跳转方法定义处。
如果你的编辑器没有这种跳转功能还是要用搜索方式找到ShowMenu。


  1. function ShowMenu(menuItem, numItem, numShow, x1, y1, x2, y2, isBox, isEsc, size, color, selectColor)
复制代码


源码我就不贴了,这个方法非常非常的长要不咱们就到这。。。。不过源码里也给了非常详细的注释,如果有能力凭注释说明看动内部代码那也的确没必要再看此贴了。
其他人,我们继续来一步一步解析。


大体上可以分为绘制逻辑控制逻辑








2.1 绘制逻辑


  1. function ShowMenu4(menuItem, numItem, numShow, x1, y1, x2, y2, isBox, isEsc, size, color, selectColor)
  2.     ...(省略若干代码)

  3.     local surid = lib.SaveSur(0, 0, CC.ScreenW, CC.ScreenH);

  4.     while true do
  5.         if num ~= 0 then
  6.             lib.LoadSur(surid, 0, 0);
  7.             if isBox == 1 then
  8.                 DrawBox(x1, y1, x1 + w, y1 + h, C_WHITE);
  9.             end
  10.         end

  11.         for i = start, start + num - 1 do
  12.             local drawColor = color; --设置不同的绘制颜色
  13.             if i == current then
  14.                 drawColor = selectColor;
  15.                 lib.Background(x1 + CC.MenuBorderPixel, y1 + CC.MenuBorderPixel + (i - start) * (size + CC.RowPixel),
  16.                         x1 - CC.MenuBorderPixel + w, y1 + CC.MenuBorderPixel + (i - start) * (size + CC.RowPixel) + size, 128, color)
  17.             end
  18.             DrawString(x1 + CC.MenuBorderPixel, y1 + CC.MenuBorderPixel + (i - start) * (size + CC.RowPixel),
  19.                     newMenu[i][1], drawColor, size);

  20.         end

  21.         if JY.Status == GAME_START then

  22.             DrawString(10, 10, "金庸群侠传『SRPG』复刻原版 v0.41", C_ORANGE, CC.Fontsmall)
  23.             DrawString(10, 10 + CC.Fontsmall + CC.RowPixel, "MOD移植:苍天泰坦", C_ORANGE, CC.Fontsmall)
  24.             DrawString(10, 10 + (CC.Fontsmall + CC.RowPixel) * 2, "挑战模式作者:JY027", C_ORANGE, CC.Fontsmall)
  25.             DrawString(10, 10 + (CC.Fontsmall + CC.RowPixel) * 3, "企鹅交流群:63627774", C_ORANGE, CC.Fontsmall)
  26.         end

  27.         ShowScreen();
  28.         keyPress = WaitKey(1);
  29.         lib.Delay(100);
  30.         
  31.     end

  32.     ...(省略若干代码)
  33. end
复制代码

上面一堆local开头的变量声明可以先不看,其中有一行lib.GetKey()其实放这里没有任何意义,想做代码优化时可以删掉。

尽可能的删除了不相干的代码。
这里进入了一个while条件循环,条件是true就是一个死循环,一直无限循环执行内部代码,直到我们主动跳出。
这样的逻辑就保证了在标题页面玩家可以一直对三个按钮做控制。


而跟随控制按钮的样式也发生了变化,所以绘制逻辑也放在了循环里,保证你按一下变一次。

2.1.1 画面缓存的利用

  • lib.SaveSur 缓存画面,返回给你一张id存折(不是
  • lib.LoadSur 读取画面,递出存折,把当时存的画面还给我
  • lib.FreeSur 这个画面我也不需要了,丢掉吧

lib.SaveSur参数也是非常经典的起始坐标和宽高,坐标我们熟悉了,都是0那就是屏幕左上角的原点。CC开头的是游戏中会经常用到的数据,通过它我们很方便的拿到了游戏窗口的宽CC.ScreenW,高CC.ScreenH。那么,第一行代码里我们把整个窗口做了一次快照缓存
你能在脑海中想出这个缓存应该是什么样子吗

没错,就是很朴素的一张背景图而已,因为在调用ShowMenu之前,我们只调用了一个绘制代码lib.LoadPicture,还记得是哪里调用的吗?





之所以这样做,都是为了实现“按一下按钮改变一次画面”。
代码没有任何智慧,所有行为都是开发者在控制,你脑中去想“按方向键下,第二个选项高亮”是不行的,我们需要确切的用代码告诉电脑“清理窗口里所有像素,普通方式画第一个选项,以高亮方式画第二个选项,普通方式画第三个选项”


你可能会疑惑,为什么要清理所有像素,不是只需要改第二个选项高亮吗?
现在,你就要建立一个思维,每一帧都是独立绘制的,绝对绝对不要在帧与帧之间建立状态关系。这看起来是浪费性能,其实不然,你如果记录状态去修改绘制局部,你会发现你将陷入逻辑深渊无法自拔。


无法理解也没关系,只需要记住,直接刷新画板再画是最能保证代码效率和安全的方式。


既然每一帧都要清空画面再画那三个按钮,作为一个通用函数,你是不知道之前的画面是什么样子,如果一起清掉了结果就是窗口里一片漆黑,只留下三个大按钮。


为了防止这个情况我们就调用lib.SaveSur把当前已经绘制的画面缓存下来,每次重新画按钮之前把它画上。


2.1.2 画面帧数


说起帧,应该都理解就是一秒内画面刷新了几次。
  1.     while true do

  2.         ShowScreen();
  3.         keyPress = WaitKey(1);
  4.         lib.Delay(100);

  5.     end
复制代码


我们已经处在了一个死循环内,其他任何代码都不会执行了,所以此时游戏有多少帧,取决于这个while运行有多快。
其他的代码都是微秒级的执行速度,我们可以忽略不计。


  • ShowScreen()的作用就是把我们画好的画板提交到显示器上来,也就是调一次就是一帧,调用之前屏幕上的像素不会有任何变化。
  • WaitKey(1)就是等待按键,这个留到控制逻辑的部分再详细解析。
  • lib.Delay(100)休息100毫秒,100毫秒后往下执行。


基于以上可以判断,调用ShowMenu方法后,游戏的帧数是不确定的,取决于玩家按键频率,但最快也是10帧,因为有一个100毫秒休息的限制。


2.1.3 画选项


  1.         for i = start, start + num - 1 do
  2.             local drawColor = color; --设置不同的绘制颜色
  3.             if i == current then
  4.                 drawColor = selectColor;
  5.                 lib.Background(x1 + CC.MenuBorderPixel, y1 + CC.MenuBorderPixel + (i - start) * (size + CC.RowPixel),
  6.                         x1 - CC.MenuBorderPixel + w, y1 + CC.MenuBorderPixel + (i - start) * (size + CC.RowPixel) + size, 128, color)
  7.             end
  8.             DrawString(x1 + CC.MenuBorderPixel, y1 + CC.MenuBorderPixel + (i - start) * (size + CC.RowPixel),
  9.                     newMenu[i][1], drawColor, size);

  10.         end
复制代码


绘制逻辑最主要的部分来了,真正把按钮画出来就是这段代码。
在我省略的代码中,做了一系列计算来确定按钮的位置,一个窗口说大不大,但你要把你的按钮摆在正确的坐标上还是需要考虑很多的,尤其如果是通用函数,要考虑更多变数,例如:选项数量变化的影响,选项文字大小的影响,排版方式的影响等等。


我之所以省略,实在觉得解析没有意义,坐标系的计算都是初中就学的东西
for循环次数就是依据按钮个数,能画几个按钮就循环几次,具体多少次怎么计算出来的,其实也没必要太关心,不做无意义解析。


lib.Background(x1,y1,x2,y2,bright,color) 是一个绘制颜色的底层实现,比lib.FillColor多了一个bright参数,其他参数是一样的用法,bright是亮度,取值范围0-255。突然发现这个方法并没有起作用,没有画出应该画的颜色,没关系用其他开源mod时肯定是有效果的。


DrawString(x, y, str, color, size, flag)用来绘制文字,其内部调用lib.DrawStr底层实现,为了实现同一行文字有不同颜色的效果做了封装。参数都很好理解,坐标,文字,颜色,大小,只有最后一个flag没啥用,直接不传即可。


2.1.4 版本信息


  1.         if JY.Status == GAME_START then

  2.             DrawString(10, 10, "金庸群侠传『SRPG』复刻原版 v0.41", C_ORANGE, CC.Fontsmall)
  3.             DrawString(10, 10 + CC.Fontsmall + CC.RowPixel, "MOD移植:苍天泰坦", C_ORANGE, CC.Fontsmall)
  4.             DrawString(10, 10 + (CC.Fontsmall + CC.RowPixel) * 2, "挑战模式作者:JY027", C_ORANGE, CC.Fontsmall)
  5.             DrawString(10, 10 + (CC.Fontsmall + CC.RowPixel) * 3, "企鹅交流群:63627774", C_ORANGE, CC.Fontsmall)
  6.         end
复制代码
绘制逻辑剩余的部分就是标题界面看到的左上角的版本信息,不喜欢的可以删掉






2.2 控制逻辑

如果没有控制逻辑,画面永远都是定死的。控制逻辑如同点名,老师每一帧都看一遍全班同学的名单(老师好累),然后想点谁就点谁,只有点到的名的请站起来这种概念。


  1.     while true do

  2.         for i = start, start + num - 1 do

  3.             if i == current then
  4.                 drawColor = selectColor;
  5.             end

  6.         end

  7.         keyPress = WaitKey(1);
  8.         
  9.         if keyPress == VK_ESCAPE then
  10.             if isEsc == 1 then
  11.                 break
  12.             end
  13.         elseif keyPress == VK_DOWN then
  14.             current = current + 1;
  15.         elseif keyPress == VK_UP then
  16.             current = current - 1;
  17.         end
  18.     end
复制代码

我们暂时省略绘制逻辑的代码,保留控制逻辑相关代码。
for循环里,谁高亮是current决定的,我们控制的就是current的值。
三个按钮,那么就让current在1到3之间变化,是多少就会让第几个按钮亮起来。


WaitKey()方法是阻塞式方法,即使你是死循环,运行到这里也要乖乖停下来,一直等到有玩家按下按键才能继续往下循环下去。你想到了什么吗,没错它内部也是一个死循环,一个等待玩家按键才能跳出的死循环。


拿到按键值之后做了一系列判断,如果是“下”current 加1,如果是“上”current 减1,左右方向我们没必要处理,按下回车则可以跳出循环,并返回给调用者选择的值是多少,调用者再根据返回值判断玩家选了哪个,再做进一步的逻辑处理。


搜索VK_UP定义来源,你还会发现其他很多键盘按键的定义,26个字母和常用功能键都有了。
代码中其他的888,999等逻辑就不看了,或者是特殊的确认方式,或者是对选项坐标y值的控制,还是那句话,当你想做这种逻辑时自然会写的,强行读懂别人的没有意义,基础的控制逻辑理解后就没有什么效果实现不了。


3.总结

这次我们从头到尾解析了一个通用函数,之后会不断的看到它的出现。解析完后对帧绘制这件事有了更深入的理解,对你以后子定制画面会有极大帮助。
那么不如我就留一个小作业,看看你是否真的掌握了如何在想要的位置上画出想画的东西


请尝试实现以下效果:
1.菜单按钮改横向布局,横向上居中
2.按左右进行选择,选中时有边框





一些提示:
  • DrawBox()绘制边框
  • 横向时文字高度相等,只有x坐标在变化
  • 完整复制ShowMenu方法并重命名为ShowMenu5,自己修改着玩的时候不要随意更改原代码
  • lib.Background方法可能无效,我们先不使用这个方法了。

【武侠.中国】铁血丹心论坛(大武侠):致力于推广和发展武侠文化,让我们一起努力,做全球最大的武侠社区。
可能是目前为止最好的金庸群侠传MOD游戏交流论坛,各种经典武侠游戏等你来玩,各种开源制作工具等你来实现你的游戏开发之梦。
发表于 2022-11-20 20:39 | 显示全部楼层
感谢大佬分享!
【武侠.中国】铁血丹心论坛(大武侠):致力于推广和发展武侠文化,让我们一起努力,做全球最大的武侠社区。
可能是目前为止最好的金庸群侠传MOD游戏交流论坛,各种经典武侠游戏等你来玩,各种开源制作工具等你来实现你的游戏开发之梦。
发表于 2022-11-22 11:55 来自手机 | 显示全部楼层
请问R数据如何用lua文件实现,龙的传人版区有一个帖子说有一个叫"彪帝"将R数据转成几个lua文件,这个如何编写给个样板吧!
【武侠.中国】铁血丹心论坛(大武侠):致力于推广和发展武侠文化,让我们一起努力,做全球最大的武侠社区。
可能是目前为止最好的金庸群侠传MOD游戏交流论坛,各种经典武侠游戏等你来玩,各种开源制作工具等你来实现你的游戏开发之梦。
 楼主| 发表于 2022-11-22 19:39 | 显示全部楼层
syongbn 发表于 2022-11-22 11:55
请问R数据如何用lua文件实现,龙的传人版区有一个帖子说有一个叫"彪帝"将R数据转成几个lua文件,这个如何编 ...

我个人是不太赞同这种做法,保持R数据与存档的绑定关系是很有必要的。

你要转的话,可以利用其他mod,黑山已经写好了一套R数据的lua表结构
【武侠.中国】铁血丹心论坛(大武侠):致力于推广和发展武侠文化,让我们一起努力,做全球最大的武侠社区。
可能是目前为止最好的金庸群侠传MOD游戏交流论坛,各种经典武侠游戏等你来玩,各种开源制作工具等你来实现你的游戏开发之梦。
发表于 2022-11-24 13:43 | 显示全部楼层
牛牛牛!不错的MOD。
有个问题:挑战模式,系统设置能保存下来吗,每次重开,又还原了。

另外,挑战模式,王语嫣不能收?都打到30级,收了周伯通、萧峰等,但东方怎么一直不出?
【武侠.中国】铁血丹心论坛(大武侠):致力于推广和发展武侠文化,让我们一起努力,做全球最大的武侠社区。
可能是目前为止最好的金庸群侠传MOD游戏交流论坛,各种经典武侠游戏等你来玩,各种开源制作工具等你来实现你的游戏开发之梦。
发表于 2022-11-25 16:14 | 显示全部楼层
学习了很有用非常感谢
【武侠.中国】铁血丹心论坛(大武侠):致力于推广和发展武侠文化,让我们一起努力,做全球最大的武侠社区。
可能是目前为止最好的金庸群侠传MOD游戏交流论坛,各种经典武侠游戏等你来玩,各种开源制作工具等你来实现你的游戏开发之梦。
发表于 2022-11-25 20:37 | 显示全部楼层
本帖最后由 woabclf 于 2022-11-25 20:40 编辑

感谢楼主的教学,我刚好想问你一个问题,你知道lib系列的函数内容是要到哪里修改?

例如lib.LoadPicture或是lib.Delay、lib.FillColor等等的,script资料夹好像找不到。

有些Mod的lib.PicLoadCache格式用法有些差异。



【武侠.中国】铁血丹心论坛(大武侠):致力于推广和发展武侠文化,让我们一起努力,做全球最大的武侠社区。
可能是目前为止最好的金庸群侠传MOD游戏交流论坛,各种经典武侠游戏等你来玩,各种开源制作工具等你来实现你的游戏开发之梦。
 楼主| 发表于 2022-11-26 18:48 | 显示全部楼层
lxw6 发表于 2022-11-24 13:43
牛牛牛!不错的MOD。
有个问题:挑战模式,系统设置能保存下来吗,每次重开,又还原了。

mod作者是苍天泰坦,其实我也没怎么玩过。

只是觉得这一版本源码相对来说比较干净原始,用来做入门学习还不错。
【武侠.中国】铁血丹心论坛(大武侠):致力于推广和发展武侠文化,让我们一起努力,做全球最大的武侠社区。
可能是目前为止最好的金庸群侠传MOD游戏交流论坛,各种经典武侠游戏等你来玩,各种开源制作工具等你来实现你的游戏开发之梦。
 楼主| 发表于 2022-11-26 18:51 | 显示全部楼层
woabclf 发表于 2022-11-25 20:37
感谢楼主的教学,我刚好想问你一个问题,你知道lib系列的函数内容是要到哪里修改?

例如lib.LoadPicture ...

lib函数都是c++端实现,也就是启动游戏的exe文件。

你想要自己修改就下载源码自己编译,论坛里有发布C++源码帖子,可以找找看
【武侠.中国】铁血丹心论坛(大武侠):致力于推广和发展武侠文化,让我们一起努力,做全球最大的武侠社区。
可能是目前为止最好的金庸群侠传MOD游戏交流论坛,各种经典武侠游戏等你来玩,各种开源制作工具等你来实现你的游戏开发之梦。
发表于 2022-11-27 18:25 | 显示全部楼层
决定吃小菜 发表于 2022-11-26 18:51
lib函数都是c++端实现,也就是启动游戏的exe文件。

你想要自己修改就下载源码自己编译,论坛里有发布C ...

金群Mod的exe要用什么软件开比较好?要反编译主程式exe吗?

是不是还有dll档?你知道要怎么查看dll?要用什么软件开?
【武侠.中国】铁血丹心论坛(大武侠):致力于推广和发展武侠文化,让我们一起努力,做全球最大的武侠社区。
可能是目前为止最好的金庸群侠传MOD游戏交流论坛,各种经典武侠游戏等你来玩,各种开源制作工具等你来实现你的游戏开发之梦。
 楼主| 发表于 2022-11-29 05:18 | 显示全部楼层
woabclf 发表于 2022-11-27 18:25
金群Mod的exe要用什么软件开比较好?要反编译主程式exe吗?

是不是还有dll档?你知道要怎么查看dll? ...

有源码啊,源码是c++工程,论坛里已经发了,VistualStudio编译,具体用的版本源码发布贴里作者应该会有说明。
【武侠.中国】铁血丹心论坛(大武侠):致力于推广和发展武侠文化,让我们一起努力,做全球最大的武侠社区。
可能是目前为止最好的金庸群侠传MOD游戏交流论坛,各种经典武侠游戏等你来玩,各种开源制作工具等你来实现你的游戏开发之梦。

本版积分规则

小黑屋|手机版|铁血丹心

GMT+8, 2024-11-23 18:16

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表