铁血丹心

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

[lua复刻] 黑山群侠传学习1

[复制链接]
 楼主| 发表于 2024-6-16 09:42 | 显示全部楼层 |阅读模式

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

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

x
# 下载
论坛有资料可以下载,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()类似,加入了鼠标滚轮和上下翻页,用于切换不同页

```
【武侠.中国】铁血丹心论坛(大武侠):致力于推广和发展武侠文化,让我们一起努力,做全球最大的武侠社区。
可能是目前为止最好的金庸群侠传MOD游戏交流论坛,各种经典武侠游戏等你来玩,各种开源制作工具等你来实现你的游戏开发之梦。
发表于 2024-8-16 13:26 | 显示全部楼层
也想学一点东西,但是看上去太复杂了,辛苦了虾米
【武侠.中国】铁血丹心论坛(大武侠):致力于推广和发展武侠文化,让我们一起努力,做全球最大的武侠社区。
可能是目前为止最好的金庸群侠传MOD游戏交流论坛,各种经典武侠游戏等你来玩,各种开源制作工具等你来实现你的游戏开发之梦。

本版积分规则

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

GMT+8, 2024-10-18 13:53

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

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