# 下载
论坛有资料可以下载,C是层是一个jysdl-bh-master压缩包,里面是很多底层的库和源码。黑山群侠传直接下载就是了,因为是学习使用因此PC版本的最好,安卓的还没试过。
# 尝试编译
1. 下载后必须要阅读一下doc/修改简明指南.txt,里面对工程的结构使用方法都有不少说明。对lua脚本的调用也作出了说明。所以首先就是尝试编译当前工程,废话不多说, Let's go。
# 编译
1. 本工程编译使用的是Visual studio编译,因此需要安装一个Visual studio。安装好后再安装一个SDK, 我安装的是v143。打开下载目录下的vc6/jysdllua.sln。使用的SDK需要设置一下。项目属性->配置属性->常规->平台工具集,选择一个自己已经安装的工具集。
2. 下面这步就有点坑了。选择编译会提示找不到lua52.lib。我上“https://luabinaries.sourceforge.net/”下载了Lua5.2版本的还是有问题,有一些引用找不到实现。最后是下载Lua5.4.0版本的才编译通过了。选择Lua 5.4.0 - Release1 -> Windows libraries -> Static -> lua-5.4.0_Win32_vc16_lib.zip。下载好后解压到Lua目录,解压出来的lua54.lib放到Lua/lib/目录下,lua54.lib改名为lib52.lib。visual studio中就可以生成执行程序了。
3. 生成的exe文件还不能直接运行,因为程序中还要求有一些动态库。所以可以下载黑山群侠传实际游戏,将生成的exe复制过去,就可以实际运行了。这里生成的exe只是游戏运行的低层逻辑,实际的游戏逻辑都以lua脚本实现。
## 黑山群侠传实践
1. 下载电脑版本的黑山群侠传,解压后可以将新编译生成的"The Fall of Star"文件复制到解压文件中测试一下编译生成的底层C是否可用。网上我只找到2017版v1.37,学习无所谓了。
2. 解压后目录如下:
.
-- DATA 主要数据,图标,头像,存档,其中的grp,idx都是UPedit生成的。存档也是grp,idx形式存在,然后用zip打包的。可以找一个save目录下的存档加个.zip后缀,再解压就会得到3个.grp结尾的文件,这三个文件就是当前你的运行数据文件,不过是以二进制方式保存的,UPedit可以打开或编辑
-- FONT 顾名思义字库,不用太大意
-- PIC Picture缩写图片,打开可以看到一个开机画面和一个死亡两面,另外几个就是按键了
-- SCRIPT 脚本文件,就是学习比较重要的内容了,这下面3个目录:CEvent, Help, ItemInfo也是UPedit生成的,使用UPedit时重点关注
-- SOUND 音乐没啥说的
-- UPedit 这个是很多资源的编辑工具,比如游戏人物,武功,场景,剧情都可以使用这个编辑器
-- 其它就是各种动态库和电子书了
3. 上面比较重要的就2个目录UPedit和SCRIPT, 前面是个游戏编辑工具,很重要游戏中很多元素都是由它生成的。SCRIPT中就是游戏的一些逻辑了,熟悉了后就可以各种魔改,加功能了。
3. UPedit我也需要认真学习下,本人就先从SCRIPT下手
4. 几个重要脚本
- DIY.lua 官方建议自己练手的代码就可以加入到这个文件,当然也可以是其它任意文件名,最后只要包含到jymain.lua中调用就可以了
- jyconst.lua 这个就是一些全局变量,这个文件就很大了3600多行,但也非常重要,比如文件运行过程中有那些变量会改变啊,支持那些键盘按键啊,颜色啊,各种配置文件位置名称,存档包含内容长度,图片序号等。内容太多太复杂就不一一说了,用到了再自行查看。
- jymain.lua 这个就重要了,基本游戏中所有的画面都能在这里面找到函数对应,怎么走路,怎么进入游戏,怎么设置属性,怎么判断学习武功是否有组合都在这个里面,当然功能多,自然内容也多,近10000行。需要慢慢玩。
- jywar.lua 听名字就知道是打架的。这个就更大了,先不管它的,这么大不看得满头是包
下面就开始看看jymain.lua有些啥,可以如何DIY
## jymain.lua主游戏逻辑文件
1. 从最上面开始IncludeFile是包含其它脚本的,后面在新文件中加了新功能,就要在这个函数中引入进来。
2. SetGlobal设置全局变量,可以看看有那些全局变量。这里面的变量都比较重要,基本游戏全程都在使用,所以有必要理解下,不过第项后面都有注释我就不画蛇添足了。这里只是变量初始化,后面会改变这此变量。
3. JY_Main只是用于调用JY_Main_sub(),myErrFun(err)只是用于打印调试信息的不管。
4. JY_Main_sub()程序主入口。while之前都是初始化一些内容不用太关心。一路到StartMenu(),看名字应当是开始菜单。
5. StartMenu(),进入函数就有3个选项,看似注释了,不知道有没有用,改下重新开始的文字运行看下。果然没用,显示的三个菜单是大梦初醒,梦往神游,南柯一梦。研究下这个菜单。调用的是TitleSelection()
5. TitleSelection()就有点意思了,这里将代码贴出来说。
```bash
/*
* @brife : 显示菜单,返回选中的菜单项编号
* @param : none
* @return : 选中的菜单项编号1 ~ 3
*/
function TitleSelection()
local choice = 1
-- 这个就是核心内容
-- 第一列是没有选中时显示的图片编号
-- 第二列是选中后显示的图片编号
-- 第三列,第四列是图片显示起点
-- 最后两个应当是图片显示终点
local buttons ={
{961,964,351,340,613,387},
{962,965,351,405,613,452},
{963,966,351,470,613,517}
}
-- 定义一个局部函数, 当前鼠标位置是否在按键上
local function on_button(mx, my)
local r = 0
for i = 1, #buttons do
if mx >= buttons[3] and mx <= buttons[5] and my >= buttons[4] and my <= buttons[6] then
r = i
break
end
end
return r
end
while true do
if JY.Restart == 1 then
return
end
-- 获取按键或鼠标事件,这里的ktype用的数字表示有点迷惑
-- 需要结合底层库才能很清楚明白什么意思
local keypress, ktype, mx, my = lib.GetKey()
-- 键盘向下按键按下
if keypress == VK_DOWN then
choice = choice + 1
if choice > #buttons then
choice = 1
end
-- 键盘向上按键按下
elseif keypress == VK_UP then
choice = choice - 1
if choice < 1 then
choice = #buttons
end
-- 鼠标移动或鼠标左键按下
else
if (ktype == 2 or ktype == 3) then
-- 判断是否在一个按键图片上
local r = on_button(mx, my)
if r > 0 then
choice = r
end
end
-- 鼠标按下一个按键或键盘上选中后按下确认
if keypress == VK_RETURN or (ktype == 3 and on_button(mx, my)>0) then
break
end
end
Cls()
-- 根据buttons确定显示图片编号
for i = 1, #buttons do
-- 默认为不选中图片
local picid = buttons[1]
if i == choice then
-- 唯一一张选中图片
picid = buttons[2]
end
lib.LoadPNG(1, picid * 2 , buttons[3], buttons[4], 1)
end
--版本号
DrawString(CC.ScreenW-115,CC.ScreenH-30,CC.Version,C_BLACK,CC.Fontsmall)
ShowScreen()
lib.Delay(CC.Frame)
end
return choice
end
```
6. 知道上面内容就可以更改图片了,最简单的方法就是更改buttons内容,将第一行前2列改来和第二行一样,运行前2个菜单图片应当就会显示一样的了。这里的图片是直接读取的DATA/head/目录下的文件,可以看到961~966就是菜单选择的图片。
7. 继续StartMenu(),选择了要不要开始游戏,当然就是要开一个新游戏了,所以就来到了NewGame()。
8. 开新游戏了。450多行干了些啥? 清屏,加载了一个默认的游戏数据。可以看看默认游戏数据设了些啥。
9. 跳过去一看大意了。LoadRecord(0)和加载游戏数据合并了,有点大。后面再看182行,还是继续看新游戏界面。
10. 从文字可以明显看到就是选择难道的界面,这里只看到文字提示,运行游戏发现应当还有6个菜单才对,找一下菜单在那里。MODEXZ2是什么看看,在jyconst.lua中搜索找到:MODEXZ2 = {"入门", "少侠", "大侠","掌门","宗师","传说"},看来底层就是通过这个表显示的内容。可以改下这里文字试下。
11. 同理下面是先主角类型,如果选的标准主角,就会直接给属性了,标准主角下还给了一个特殊主角"无酒不欢",可以自己diy一个变态主角。还发现一个有趣的事,作者”无酒不欢“把男特殊主角头像隐藏起来了,来找找特殊男主角头像怎么换。
12. 新游戏中特殊男主”无酒不欢“头像设置的是355,但后面还有一个头像确认,在函数say(s, pid, flag, name)中,怀疑最终的图像是由say()函数确定。试着改一下,还是不行,再次搜索290,发现有好几个地方都出现了290。最后发现应当是2000多行的ShowPersonStatus_sub()中会对状态栏中的起作用,say()中的会对对话的起作用。为了统一完全可以使用一个常量替换了,不然就有点懵逼了,初始设置355,后面全是显示的290。
13. 下面就来试着设置特殊男/女主头像。
14. 统一特殊男女主角头像。DIY.lua中加入如下代码:
```bash
function DIYInclude()
SPECIAL_MAN_PIC_ID = 290 -- 特殊男主角头像ID
SPECIAL_WOMAN_PIC_ID = 368 -- 特殊女主角头像ID
end
```
15. 将newGame(),ShowPersonStatus(), ShowPersonStatus_sub(), say()中出现290/368的地方使用上面定义的SPECIAL_MAN_PIC_ID/SPECIAL_WOMAN_PIC_ID替换,打开游戏确定OK。
16. 继续看NewGame()下面的内容。有个SetS(10, 0, 6, 0, 1)只看名字不知道干什么的,看看底层的SetS函数,看了下是将当前选择存起来,表示是标准主角。后面的主系数也存起来了但全是数字看着太费劲了。
17. 后面是畅想主角选择,加载1000编号的背景图片。先将所有人物的姓名和畅想分阶加入到menu表里面,再用ShowMenu3显示出来。
18. 看看ShowMenu3(menu, menuSize, numShow, showRow, x1, y1, size, color, selectColor)。其中menu, munuSize, x1, y1, color, selectColor可以猜出来什么意思。其它不明白的再向下看。
19. 还是代码里面说明方便
```bash
function ShowMenu3(menu,itemNum,numShow,showRow,x1,y1,size,color,selectColor)
local w=0;
local h=0; --边框的宽高
local i,j=0,0;
local col=0; --实际的显示菜单项
local row=0;
lib.GetKey();
Cls();
--建一个新的table
local menuItem = {};
local numItem = 0; --显示的总数
--把可选为畅想的人物保存到新的table, 畅想分阶为0表示不可选排除
for i,v in pairs(menu) do
if v[3] ~= 0 then
numItem = numItem + 1;
menuItem[numItem] = {v[1],v[2],v[3],i}; --注意第4个位置,保存i的值
end
end
--计算实际显示的菜单项数
if numShow==0 or numShow > numItem then
col=numItem;
row = 1;
else
--列数
col=numShow;
--(项目总数-1)/列数=行数
row = math.modf((numItem-1)/col);
end
if showRow > row + 1 then
showRow = row + 1;
end
--计算边框实际宽高, 名字最长长度
local maxlength=0;
for i=1,numItem do
if string.len(menuItem[1])>maxlength then
maxlength=string.len(menuItem[1]);
end
end
-- 中文长度要除以2表示实际显示长度
-- 先计算每行需要的宽度:(每个名字*名字大小+名字间隔)*名字个数+每行的边框厚度
w=(size*maxlength/2+CC.RowPixel*2)*col+2*CC.MenuBorderPixel;
-- 高度就要容易些了:(每个字大小+上下行间隔)*显示行数+边框厚度
h=showRow*(size+CC.RowPixel*2) + 2*CC.MenuBorderPixel;
-- 下面就是具体显示逻辑,看到这里就要怀念C的大括号了,这里的一堆end看得眼睛疼,还不方便确定那一块是一起的
-- 下面这个是变量也多,功能也不少,头疼
local start=0; --显示的第一项
local curx = 1; --当前选择项
local cury = 0;
local current = curx + cury*numShow;
local returnValue =0;
-- 这里类似于创建一个空白的画布,指定了长和宽
local surid = lib.SaveSur(0, 0, CC.ScreenW, CC.ScreenH)
-- 这个最外层循环用于重复显示的,因为显示的名字可能一页显示不完
-- 当显示了第二页又要回滚到第一页时就需要它循环显示了
while true do
if JY.Restart == 1 then
break
end
-- 加载画布
if col ~= 0 then
lib.LoadSur(surid, 0, 0)
end
--说明行在此
DrawString(x1+(size+CC.RowPixel)*9-3,y1-(size+CC.RowPixel)*2-CC.RowPixel,"PgUp/PgDn/鼠标滚轮翻页",LimeGreen,size);
DrawString(x1+(size+CC.RowPixel)*8,y1-(size+CC.RowPixel)*1,"名称黄色为一阶 名称蓝色为高阶",LimeGreen,size);
-- 每页的显示
for i=start,showRow+start-1 do
for j=1, col do
-- 计算共显示了几个名字了
local n = i*col+j;
if n > numItem then
break;
end
--设置不同的绘制颜色
local drawColor=color;
if menuItem[n][3] == 2 then
drawColor = M_DeepSkyBlue
end
local xx = x1+(j-1)*(size*maxlength/2+CC.RowPixel*2) + CC.MenuBorderPixel
local yy = y1+(i-start)*(size+CC.RowPixel*2) + CC.MenuBorderPixel
-- 选中的人物有背景色
if n==current then
drawColor=selectColor;
lib.Background(xx-5, yy-5, xx + size*maxlength/2, yy + size+5, 128, color)
end
-- 显示名字
DrawString(xx,yy,menuItem[n][1],drawColor,size);
end
end
-- 下面就是和TitleSelection()类似,加入了鼠标滚轮和上下翻页,用于切换不同页
```
|