2. 创建拓展包与新武将#

2.1. 扩展包的内容#

如在上一章所述,新月杀的游戏扩展文件全部位于packages文件夹。我们先看看游戏中现有的扩展是怎样的。打开packages/shzl(神话再临):

../../_images/1-1.png

拓展包shzl的内部文件#

2.1.1. init.lua#

init.lua文件是扩展包的核心,严格意义上,扩展包只会加载init.lua,所以扩展中其他所有的lua文件都需要通过init.lua来加载。打开shzl的init.lua:

 1require("packages.shzl.i18n.en_US")
 2
 3local prefix = "packages.shzl.pkg."
 4
 5local wind = require (prefix.."wind")
 6local fire = require (prefix.."fire")
 7local forest = require (prefix.."forest")
 8local mountain = require (prefix.."mountain")
 9
10local shadow = require (prefix.."shadow")
11
12local thunder = require (prefix.."thunder")
13
14local god = require (prefix.."god")
15
16Fk:loadTranslationTable{ ["shzl"] = "神话再临" }
17
18return {
19    wind,
20    fire,
21    forest,
22    mountain,
23    shadow,
24    thunder,
25    god,
26}

我们可以看到通过require,对应目录下的wind风、fire火等子扩展包都加载进来了。同理,也可以像标准包一样require要用的翻译文件、辅助函数。总而言之,只有通过require才能加载init以外的lua。

我们简单地解析一下这几行代码的作用,以第1行为例:

local prefix = "packages.shzl.pkg."
local wind = require (prefix.."wind")

这行代码的意思是“定义一个局部变量——wind,然后将require的值赋给wind”,

  • wind前面的local代表的就是它是 局部变量 ,没有了local,它就是 全局变量 。 全局变量和局部变量都是 变量 。变量在使用前,需要在代码中进行 声明 ,即创建该变量;在lua里面, local a,就是创建了一个局部变量 a。 变量可以被访问,我们可以拿他来参与运算,也可以再赋值。 一般而言,要 慎用全局变量 ,因为它们很容易相互打架,造成污染。全局变量会被编辑器标为红色。

  • =便是编程中最常见的 赋值 符号,代表将右边的东西赋给左边。(可不是相等的意思,相等是 == )在lua里面, local a = 5 ,就是创建了一个局部变量 a 并用 5 为其赋值。

  • require则是本行代码最重要的部分,它返回之后紧跟着的双引号内的lua文件所代表的全部东西,什么都有。 下面几行都是相同的道理,分别载入了风火林山阴雷神7个子扩展包,每个文件都有各个子扩展包的武将,例如曹仁、张角就在packages/shzl/pkg/wind/init.lua文件里面。

  • init.lua或者别的lua的最后都会有一个return ,意思是返回整个模块,代表对整个文档的总结。 这里返回了我们在上方定义的所有引用,并用{}包成了一个整体。 return中的 wind 就指向前面声明过了的 wind 。局部变量在声明完毕之后,引用的时候就不用 local 啦(不然就变成再创建一个局部变量了)。

每个扩展包lua都需要return一个扩展包,否则这个扩展包就是不存在的。

提示

require 会将全局变量也加载进来,因此代码中要慎用全局变量。

提示

当然也可以在init.lua塞进扩展包全部的内容,standard标准包就是这样的。理论上这也可以,但是还是分开比较好,尤其是有下文所述限制的前提下。

2.1.2. 子扩展包#

一个大的扩展包系列会含有多个小扩展包,例如神话再临的风林火山,一将成名的2011~……等。子扩展包是游戏中武将一览的划分区间。这样做既是为了分类美观、查找方便,也是限制单个lua文件大小。

提示

Lua语言规定单个文件至多只能有200个局部变量,因此根据估算,我们建议一个子扩展包的武将数量不要超过40。

2.1.3. audio, image#

顾名思义是存放扩展包语音、图片的文件夹。对于我们的第一个扩展做得简单些,只需要了解:audio中的death为武将阵亡语音,skill为技能语音;image中的general为武将图片。

2.1.4. 其他文件#

包括开源声明、git托管文件等,先不管它们。

2.2. 创建拓展包#

我们模仿shzl的形式创建一个扩展。首先在packages文件夹下建立一个新文件夹。老传统,我们的第一个扩展就叫study学习包~

../../_images/1-2.jpg

新建文件夹#

然后在study文件夹中创建一个init.lua文件。可以直接从其他包复制,也可以新建一个txt文本然后改名。用VSCode打开它,但在修改之前,请先稍微动一下手,点击右下角这一块,将你的默认缩进改为空格:2, 编码改为UTF-8(默认是这个,但最好确认一下)

../../_images/1-3.jpg

注意编码必须是UTF-8,空格=2只是推荐缩进#

然后修改代码,加载子扩展包:

../../_images/1-4.jpg

新建文本文件并改名#

在study文件夹中,我们先创建一个扩展包合集pkg文件夹,这里用来存放我们所有的子扩展包。 image和audio暂时先不着急创建

../../_images/1-SP.png

新建pkg文件夹#

最后开始新建我们的xuexi子扩展包。

../../_images/1-SP2.png

新建xuexi子扩展包#

然后,让我们回到study文件夹中的init.lua文件:

1local xuexi = require "packages/study/pkg/xuexi"
2
3return {
4  xuexi,
5}

提示

  1. 我们在代码中利用到的标点都是半角符号,也就是英文输入模式下的标点。比如第1行里的引号不是 “” 而是 "" ,第4行的逗号不是 而是 , 。(当然,通过设置输入法,我们也可以在中文输入模式下打出半角符号~)

  2. 注意第4行开头的缩进。由于这一行属于下一个层级,我们需要往后缩进,让代码的结构更加清晰。在新月杀里,我们约定缩进2个空格。

  3. 以UTF-8无BOM格式编码。

  4. 所有文件尾必须留一个换行符,敲一个回车即可。(在这里的代码片段中,可能无法看到最后的空行,看示例代码更为清楚)

重要

我们现在所做的一切操作结果都可以在 引用示例.zip 中找到,可以多多参考对照。

然后我们进入study/pkg/xuexi文件夹,创建init.lua文件和一个skills文件夹: init.lua主要用来初始化我们子扩展包xuexi的内容,skills文件夹是保存子扩展包xuexi的武将技能的地方。

现在我们将xuexi文件夹下面的init.lua,修改为:

 1local extension = Package:new("xuexi")
 2extension.extensionName = "study"
 3
 4extension:loadSkillSkelsByPath("./packages/study/pkg/xuexi/skills")
 5
 6Fk:loadTranslationTable{
 7  ["xuexi"] = "学习",
 8}
 9
10return extension
  • 1行,声明以创建局部变量 extension,将其赋值为创建出的名为 xuexi 的扩展包。这里的名字是子扩展包的名字。 接下来的代码中, extension 就将代表整个子扩展包。

  • 2行为这个子扩展包,也就是 extension ,指定 extensionName ,也就是它所属的大扩展包,其名字必须为我们文件夹的名字, 整个大扩展包都是根据这个名字寻找路径的,包括lua,包括图片,所以不要填错。

  • 4行是加载函数,加入该函数后,扩展包就会自动加载skills里面所有的技能,即使是某技能没有出现在任何武将牌上,也会被加载进来。 这里的路径是相对于当前扩展包的路径,所以要用"./" 开头。

  • 6-8行是翻译表,加入该函数后,我们就可以在游戏中看见我们定义的学习扩展包变成中文了,如果没有翻译表就会以英文名称作为其的名字。。

  • 10行返回扩展包,这样程序就知道我们这个子扩展包的代码到这里就结束啦。

这样我们的扩展包就创建完成了!~之后我们的代码操作都会在这个 xuexi/init.lua文件和xuexi/skills文件夹 之内 别跑到 study/init.lua 去啦。

../../_images/1-5.png

创建好的skills文件夹和子扩展init.lua文件#

保存 好所有动过的文件。用 单机启动 打开游戏,查看武将一览,可以看到已经出现我们的学习包了,不过现在还是空的没有武将。

../../_images/1-6.jpg

学习包!说起来study还是英文呢,要不自己加一句翻译?#

注意

新的文件、修改过的代码文件都需要在 保存 之后 重新打开游戏 才能生效。我们开发新月杀一般都是在 单机启动 环境下进行的。 所以,如果你需要在电脑端游玩,请务必下载两个客户端文件,一个用于游戏,一个用于开发测试。

2.3. 创建武将#

创建新武将的话,首先也是要先想好武将的名字。 但是这件事情就简单多了,一般来说都是用TA的姓名的汉语拼音直接作为名字的。 例如曹操就是caocao,司马懿就是simayi, 当然,英文人物也可以用英文名,如某位凯撒大哥就是caesar

看起来武将的名字比扩展包的名字好写,但其实不然。 要知道,在新月杀诞生的时候,别说界限突破了,谋攻篇都出来了,到了现在(2024年),同名武将多到可以凑个军五甚至军八…… 所以对于这些同名武将,新月杀提供了一种方法来自动识别——

当你注册一个名字格式为XXXX__YYYY(这里是双下划线,如mou__caocao)的新武将时,系统会自动将这个名字沿中间切成两半:其中右边的YYYY是“武将真名”trueName,而左边的XXXX则是这个武将的特有标示,也就是“武将包名”,或称前缀。 如果两个武将拥有同样的“武将真名”,那么这两个武将就是同名武将, 例如ex__caocaomou__caocao的“武将真名”都是caocao,那么这两个武将就是同名武将。

按照传统,我们也该写个学习包专属的孙悟空的,但是……十周年和欢乐杀已经有孙悟空了(悲),所以在命名上我们也该规矩一点…… 意味着学习包的孙悟空名字最好改成st__sunwukong了,

这里前缀取的是"st",study嘛。在你自己的拓展包中,根据情况选定合适的前缀吧。

具体代码就是local sunwukong = General:new(extension, "st__sunwukong", "god", 5), 之后你的xuexi/init.lua看起来就像这样:

重要

要注意把 return extension 留在文档末尾,以下我们的操作都要在这一行 return extension 之前完成!

 1local extension = Package:new("xuexi")
 2extension.extensionName = "study"
 3extension:loadSkillSkelsByPath("./packages/study/pkg/xuexi/skills")
 4Fk:loadTranslationTable{
 5  ["xuexi"] = "学习",
 6}
 7
 8local sunwukong = General:new(extension, "st__sunwukong", "god", 5)
 9
10return extension

创建武将本质上是创建一个“武将对象”,为此,让我们看向武将对象的构造函数:

--- 构造函数,不可随意调用。
---@param package Package @ 武将所属包
---@param name string @ 武将名字
---@param kingdom string @ 武将所属势力
---@param hp integer @ 武将初始体力
---@param maxHp integer @ 武将初始最大体力
---@param gender Gender @ 武将性别
function General:initialize(package, name, kingdom, hp, maxHp, gender)

各参数的作用见上方,这里详细解释一下:

  1. package是该武将所属的扩展包,鉴于第一行已经定义了一个extension,我们就用它了。

  2. name是这个武将的名字,就用刚才起的名字"st__sunwukong"吧

    "孙悟空"是中文翻译的显示名称,而"st__sunwukong"是武将的代码名称,在所有扩展包中武将的代码名称必须是唯一的。

  3. kingdom是这个武将所属的势力,按照传统,我们填个"god",也就是神势力。

  4. hpmaxHp分别代表体力值和体力上限,maxHp默认与hp一致,而hp默认为4。

  5. gender就是武将性别,默认为General.Male(男性)。

以上代码就往学习包里塞了一位名为“st__sunwukong”的5血5上限神势力男性武将~

至此武将的创建就完成了!但是如果我们现在进入游戏的话,他会显示为英文……那就用Fk:loadTranslationTable补一下翻译吧:

 1Fk:loadTranslationTable{
 2  ["xuexi"] = "学习",
 3  ["st"] = "学",
 4}
 5
 6local sunwukong = General:new(extension, "st__sunwukong", "god", 5)
 7Fk:loadTranslationTable{
 8  ["st__sunwukong"] = "孙悟空",
 9  ["#st__sunwukong"] = "齐天大圣",
10  ["designer:st__sunwukong"] = "设计者",
11  ["cv:st__sunwukong"] = "配音演员",
12  ["illustrator:st__sunwukong"] = "画师",
13}

翻译表其实是个很纯粹的东西,你输入特定的字符串,就会根据翻译表输出对应的翻译字符。 以下是武将信息里除了武将名以外的其他信息:

  • "#武将名"是武将的称号,理论上要多长就有多长,不过考虑到实际显示,建议限制在7个字以内。

  • "designer:武将名"是武将的设计者,默认为“官方”。

  • "cv:武将名"是武将的配音演员,默认为“官方”。

  • "illustrator:武将名"是武将的画师,默认为“官方”。

这些在进入游戏后的武将一览都能看到。

保存一下,到游戏里看看我们的孙悟空吧。打开武将一览->学习包:

../../_images/1-7.jpg

我们刚刚创建的悟空在此#

这里可以看见,我们命名的代码名称为st__sunwukong,我们可以拆分一下为"st"+"__"+"sunwukong", 其中"st"是我们刚才在翻译表中添加的前缀,这个会显示在武将的右下角作为角标【学】

"__"作为连接的字符串,是用来检测角标和武将真名的

新月杀会把武将的代码名称中,检测到的第一个"__"双下划线作为连接字符,连接字符前面的就是武将角标,后面的就是武将真名。

"sunwukong"就是我们刚才定义的武将真名。

好了,赶紧拿着你新写的这位武将去单挑吧!

PS:五体力白板孙悟空的单挑能力还是不弱的呢。^_^