Forts
51 ratings
Forts模组制作指南
By 萌新肉丝Rothes and 1 collaborators
制作为Forts添加新装置、武器、材料,调整平衡性、图像和效果的模组教程 (官方翻译版)
   
Award
Favorite
Favorited
Unfavorite
译者注
  This is the Chinese translation of modding guide. Go How to mod Forts for the English one.

  本教程基于英文文档翻译,进行了一些修改,会随其同步更新。如有疏漏,请留言。

  在本教程中,如您遇见类似于 "粗体(Bold)" 这样的文字,请您花几秒钟记住此中文和括号内的英文,这是您在制作模组中可能会使用到的词语,但是刚刚的这个不需要记。

  如果您在制作模组的过程中遇到任何问题,可以在英文文档中使用英语提问,也可在此处提问,但不保证会有答复。

  2022/6/23 已同步英文版 2022/5/12 更新。
学习Lua语言
  Forts 使用Lua语言配置游戏数据。您可以通过编写自己的Lua脚本文件以修改或添加 Forts 的内容。Lua 是一种强大的、易学的脚本语言,在您翻阅 Forts 内置的脚本的同时,您可以学到很多关于 Lua 的知识,但要了解更多系统化的东西,这里推荐一个网站:

  https://www.runoob.com/lua/lua-tutorial.html

  直到看完 Lua table(表) 后,您就完全足够制作 Forts 的模组了。

  任何文本编辑器都可以编辑Lua脚本文件,例如 Windows 自带的记事本,或者更高级一点的 Notepad++[notepad-plus-plus.org]Sublime Text[www.sublimetext.com] ,使用它们能让您可以更方便地制作模组。要在模组中使用英语以外的字符,您的文本编辑器必须支持 UTF-16 Unicode 编码。
配置游戏数据
  Forts 的游戏数据可以像普通的文件一样浏览。在Steam库中右键Forts > 管理 > 浏览本地文件,data目录就存放在这里。

  在data目录中存在很多个子目录,它们根据其用处而被命名,比如devices(装置)、weapons(武器)和materials(材料)。您可以浏览这些文件夹来了解这个游戏是如何设计的。游戏会寻找特定的纯文本.lua文件来获取一场游戏需要加载的数据和资源文件。

  一个模组中最重要的脚本文件包括:
  • db/constants.lua
  • db/rules.lua
  • materials/building_materials.lua
  • devices/device_list.lua
  • weapons/weapons_list.lua
  • weapons/projectile_list.lua

  在加载一场战斗的过程中,游戏会读取这些文件然后寻找它们中指定的(table)和变量(variable)。打开 weapon_list.lua 时,您可以看到一个叫做武器(Weapons)的表。游戏会遍历这个表中的内容,寻找这场战斗中需要的武器,它们叫做内部数据(internally),即存档名(SaveName),在这其中定义了建造武器的价格、建造时长等等。

  每个武器项目都有一个叫做文件名(FileName)的值。它会指向一个包含关于武器的外观和行为的具体参数的Lua文件。它用于定义武器射击、装弹、被摧毁和其他时候的效果。其中有一个叫做子弹(Projectile)的值,这个值会引用 projectile_list.lua 的子弹表中项目的存档名,并指定武器开火时将创建的子弹。配置设备类似于配置武器,只是不需要设定开火相关的参数。配置材料的所有数据都位于 building_materials.lua 中,它们的外观(包括不同损坏程度的外观)存储在渲染图(Sprites)表中。

  您可以浏览这些材料、装置和武器、子弹的数据,以了解所需制作的数据、纹理文件和效果。虽然这里面有很多东西,但一切文件都很好地命名了。如果您有无法理解的地方,可以询问我们。了解游戏的配置数据对于您正确地制作模组至关重要。

  * 子弹: 又称弹射物。新版为了更好理解,同时也为了顺应其他射击游戏,翻译为子弹。
模组架构
  在data目录中,您可以看见一个叫做"mods"的文件夹。这里面的每一个子目录都是一个模组。打开一个文件夹,以weapon_pack(使用新版武器)为例,您会看见和data根目录中相同的一些文件夹和.lua文件。

  在Forts中,模组是这样运作的:先加载原版Lua文件,然后寻找每个激活的模组的模组目录中相同的Lua文件,再将他们在已加载内容的基础上覆盖加载。因此,每个模组都可以修改原版内容的变量和表,修改图像、子弹和效果等。它们可以添加新项目,禁用现有项目,甚至移除所有东西。

  如果您要修改某个变量,通常情况下最好要在原数值的基础上修改,加或减,乘或除,而不是直接设置一个数值。有时我们会进行一些微小的平衡性修改,如果这样的话就会影响我们以后的修改。

  注意: 请记住,您应该创建自己的模组文件夹,而不是修改原版的游戏文件或者内置模组文件。这样会影响游戏体验,导致游戏不稳定,也有可能导致在多人游戏和录像中出现不同步数据。如果不小心修改了,您可以在Steam库中右键Forts > 属性 > 本地文件 > 验证游戏文件的完整性... 以还原。
制作一个简单的模组:修改变量
  首先,我们从设置重力为零作为例子开始制作模组。控制重力的值存于db/constants.lua中一个叫做物理(Physics)的表,变量名为重力(Gravity)。默认值为981,即以每一秒游戏刻为单位的地球重力加速度大小。

  第一步,在mods目录中新建一个文件夹,把它命名为zerog,当然你非要命名为别的也行。这就是我们的模组目录。在里面新建一个文件夹"db",打开它,再新建一个文本文档,命名为"constants.lua"(.lua为后缀名)。确保这个文件存放的目录像原版数据的一样,但它放在我们的模组目录中。

  现在,我们可以修改这个文件中的数据了,但不要使用和原数据文件一样的格式,因为这会清除掉物理表中其它我们未修改的数值。我们应该像这样引用表中的一个值:

(此文件为 mods/zerog/db/constants.lua)
Physics.Gravity = 0

  同时,在 mods/zerog 中创建一个 mod.lua ,添加以下内容:

Selectable = true Category = "Physics"

  在 Forts 中打开沙盒模式。随便选一个图,然后在选择模组中点击玩家标签。正常的话你会在物理类型中看见一个名为刚刚创建的模组目录名的模组。勾上它,开始游戏。如果东西可以浮在天上,恭喜!您成功地制作了你的第一个模组。

  如您所知,我们早已制作了一个零重力模组,它的名称叫做zerogravity。这个模组不能仅仅只将重力设定为0,还修改了一些别的数据。我们还减少了牵引力,从而让飞船可以更快地移动;增加了支板允许支撑装置的最大偏转角度,以及一些其他的数据。您要知道的是,制作一个模组可能需要修改比预期所想的更多的东西。
模组加载顺序
  由于玩家可以选择大量的模组,我们修改的数据可能不是我们预先想要修改到的数据。所以我们制定了模组加载先后性的设定。首先游戏会加载原版数据的文件,然后加载第一个,第二个……这样下去。每个模组都会在它之前加载的那个模组修改后的基础上应用修改。

  加载模组的顺序由mod.lua文件中的可选参数优先级(Priority)决定。较低的值会最先加载,较高的值会晚些加载。所期望的数值范围为1-10之间,可以为小数,默认值为5。如果两个模组设定的优先级一致,则根据模组名(或创意工坊ID)的字母顺序加载。

(此文件位于 mod.lua)
-- 此模组会较晚加载 Priority = 10

  如果你的模组在需要更改每个项目的数据,你应该将它的优先级设置得大一些,从而让它最后一个被加载。例如快速建造(fast-build)模组,其修改的building_material.lua文件遍历了所有材料项目,包括在它之前加载的模组添加的,并调整了建造和回收它们需要的时间,像这样:

for k,v in ipairs(Materials) do v.BuildTime = v.BuildTime/4 v.ScrapTime = v.ScrapTime/2 end

  另外,不要依赖于未定义的数据。 如果要让新武器出现在特定位置,则应指定一个已存在的武器,获取其后的索引,然后在此处插入您的武器。 您可以在weapon_pack模组中找到示例,如将火箭炮位于电磁脉冲火箭炮之后:

table.insert(Weapons, IndexOfWeapon("rocketemp") + 1, { -- 火箭炮的数据 }
后置处理:RegisterApplyMod
  您可以计划一个函数,在所有模组都应用完毕后,游戏会调用它。它可以让您实现在正常的脚本加载调用中不太可能实现的东西,例如您编写的部分处理、计划和函数调用。这可以帮助您应用更改到在您模组之后添加的项目 - 无论优先级大小。

  它实际上适用于所有的配置脚本。在特定的装置配置脚本中,或者 device.lua 中,例如您想要双倍的生产速率,可以这么做:

-- 使用匿名函数的简单形式 RegisterApplyMod(function() EnergyProductionRate = 2 * EnergyProductionRate MetalProductionRate = 2 * MetalProductionRate end)

  指挥官使用这个系统来应用更改到模组的装置、武器、材料、子弹,以及更多其它东西。例如,建筑师(Architect)使用一个命名函数在 weapon_list.lua 中降低建造时长,像这样:

-- 命名函数,然后注册它 CommanderApplyModBase = function() for k, device in ipairs(Weapons) do if active and device.Prerequisite ~= "upgrade" then device.BuildTimeComplete = 0.25*device.BuildTimeComplete end end end RegisterApplyMod(CommanderApplyModBase)

  所有指挥官的 'ApplyMod' 函数都拥有相同的名称。如果您正在修改指挥官,想要阻止它运行,可以使用 DeregisterApplyMod 函数来移除它:

DeregisterApplyMod(CommanderApplyModBase)

  您可以自由地命名您自己的函数,以便其它模组(在需要时)可以移除您的函数。只需记住,更早加载的模组也可能定义了相同名称的一个函数。确保您为它们命名了一个独特的名称,否则您会覆写上一个模组的值。
添加包含纹理的设备
  我们创建了大型沙袋模组作为一个全面的例子,以示范如何添加新设备到Forts。订阅后,打开Steam库,浏览到 steamapps\workshop\content\410900\1293804859。这是下载的大型沙袋目录。每一个已上传到创意工坊的模组订阅后都会下载到 steamapps\workshop\content\410900 中。上传前的原文件夹名会改为一串数字,这串数字就是模组的创意工坊ID,除此之外上传前后的文件都一样。模组文件夹位置的改变就是在文件名前添加path变量如此重要的原因。请见下文。

  在这个模组中,最重要的文件是 devices/device_list.lua。在该脚本文件中,第一部分添加了基于模组内置纹理的渲染图,您可以在 ui/textures/HUD 中找到。信息渲染图(DetailSprite)和按钮渲染图(ButtonSprite)辅助函数在 data/ui/uihelper.lua 中定义,游戏会读取渲染图表以添加脚本定义的各种渲染图。游戏会指定"path"变量为模组的相对路径或绝对路径(在订阅模组的情况下是),从而允许模组引用存储在模组目录中的新纹理资源。您不需要去弄清它的工作原理,只需要在引用时使用它。如果您更改了模组文件夹名称而在文件夹中纹理文件的位置未变,模组仍会正常运行。

table.insert(Sprites, DetailSprite("hud-detail-sandbags-large", "HUD-Details-SandBagsLarge", path)) table.insert(Sprites, ButtonSprite("hud-sandbags-large-icon", "HUD/HUD-SandbagsLarge", nil, ButtonSpriteBottom, nil, nil, path))

  往下一点,您就可以看见这个模组如何添加新装置到装置表。

table.insert(Devices, IndexOfDevice("sandbags") + 1, { SaveName = "sandbags_large", -- 项目的存档名 FileName = path .. "/devices/sandbags_large.lua", -- 项目的文件名 Icon = "hud-sandbags-large-icon", -- 在主控制台中间的项目图标背景 Detail = "hud-detail-sandbags-large", -- 点击项目后在主控制台左边的信息背景 Prerequisite = "upgrade", -- 建造所需要的前置条件 BuildTimeIntermediate = 0, -- 已过时的参数,在制作模组时已不需要使用,忽略它。 BuildTimeComplete = 30, -- 建造所需时间 ScrapPeriod = 8, -- 回收所需时间 MetalCost = 20, -- 建造所需金属 EnergyCost = 800, -- 建造所需能量 MetalRepairCost = 20, -- 维修所需金属 EnergyRepairCost = 800, -- 维修所需能量 MetalReclaimMin = 0, -- 回收获得金属比率的最小值,受回收时损坏程度决定 MetalReclaimMax = 0, -- 回收获得金属比率的最大值,即无损坏时的回收比率 EnergyReclaimMin = 0, -- 回收所得能量比率的最小值,受回收时损坏程度决定 EnergyReclaimMax = 0, -- 回收获得能量比率的最大值,即无损坏时的回收比率 MaxUpAngle = StandardMaxUpAngle, -- 支板允许支撑的最大偏移角度 BuildOnGroundOnly = false, -- 是否只允许在地基上建造 SelectEffect = "ui/hud/devices/ui_devices", -- 在主控制台中选择项目时播放的音效 })

  通过使用 IndexOfDevice函数,您可以把新装置放在一个最合理的位置(例如大型沙袋在原版沙袋之后)。第三个参数就是我们要添加的新项目。我们定义了存档名、设定了装置的具体参数的文件名、图标和信息界面渲染图、前置科技树 - 升级中心,以及一些其它的基础参数(建造所需时间、花费和回收配置数据)。

  您可以打开 devices/sandbags_large.lua 看看我们设定了什么具体参数。这就是可以让新设备与其它设备表现不同的地方。您可以查看游戏内置的设备和 weapon_pack 模组已查看需要修改的变量以及您模组中最合适的参数值。您可以在一个值域中尝试所有值,从中选择一个你觉得最佳的值,而不是直接使用想定义的值。与您的好友一起测试有助于得到反馈。

  在中间,您可以看见一个渲染图表。它将沙袋的渲染图设定为模组内置的单帧纹理 /devices/SandbagsLarge.tga。再次主要,这里需要使用path变量,连接着一个正斜杠(/),由于它不会包含在path变量中。

  然后下面是构基(Root)表,它用于配置如何将多个渲染图组合在一起以组成一个装置。由于装置可以由很多部分组成,武器更是需要一个轴心点以旋转枪口,这个表可能会使用到复杂的嵌套。该架构通过创建不设定渲染图值的"Hardpoint0"子节点来指定激光或子弹的发射点。如果您要添加新武器,最好先仿制一个现有的武器,尝试用构基将渲染图组合在一起,直到您弄清它的原理。角度(Angle)、轴心点(Pivot)和轴心点偏移(PivotOffset)值决定子节点相对于父节点的位置。对象数据(UserData)用于指定为该节点将显示(或在回收过程中消失)的建造百分比。

  另外还有一个大型沙袋模组修改的与游戏相关的数据,减少20毫米口径炮对其造成的伤害。您可以在 mods\weapon_pack\weapons\projectile_list.lua 中看见。这是一个模组中的一个模组,也叫做嵌套模组。大型沙袋改变了weapon_pack的运作,因为它修改了其子弹的伤害倍率(DamageMultiplier)数据,这个数据与装置和子弹关联。如下:

cannon20mm = FindProjectile("cannon20mm") if cannon20mm then table.insert(cannon20mm.DamageMultiplier, { SaveName = "sandbags_large", Direct = 0.2, Splash = 0.2 }) end
装置升级项
  装置(包含武器)可定义一个或多个升级项,并带有可选的前置条件(Prerequisites)。您可以通过在 device_list.lua 中(如果是武器则在 weapon_list.lua 中)添加装置的升级项(Upgrades)表来实现这一功能。

  这个表中的项目设定存档名与升级价格,也设定了项目关联菜单中的按钮渲染图。如果要向一个装置添加多个升级项,您最好使用自定义的按钮渲染图以便让玩家更容易地区分它们,尽管悬停(即光标指向按钮时)升级按钮时会切换到升级项的信息渲染图并显示装置信息。

  ButtonSprite辅助函数可用于组成按钮渲染图,为按钮另外添加具有不可用、悬停和按下状态。您可以拷贝并编辑 ui/textures/context/HUD-Buttons-Blank-*.tga 中的内置纹理来制作你自己的按钮渲染图。

  您可以在开局时执行Enabled = false来禁用升级项,然后通过在地图脚本任务或模组脚本任务中通过 EnableDeviceUpgrade 或 EnableWeaponUpgrade 函数来启用升级项。

  如果您要修改一个现有设备的升级项,确保你使用插入方法而不是重新设定表的数据,以防止丢失已有的升级项和其他模组添加的升级项。

-- 此操作会寻找以下的所有纹理,以组成按钮渲染图 (path是模组的根目录): -- path .. "/ui/textures/context/HUD-Buttons-Mortar2-A.tga" -- 活动 (等待一个输入) -- path .. "/ui/textures/context/HUD-Buttons-Mortar2-D.tga" -- 不可用 (不可按下) -- path .. "/ui/textures/context/HUD-Buttons-Mortar2-R.tga" -- 悬停 (光标指向) -- path .. "/ui/textures/context/HUD-Buttons-Mortar2-S.tga" -- 选定 (光标按下) table.insert(Sprites, ButtonSprite("hud-context-upgrade-mortar2", "context/HUD-Buttons-Mortar2", nil, nil, nil, nil, path))

-- 位于 device_list.lua 或 weapon_list.lua 的一个设备表中 Upgrades = { { Enabled = true, SaveName = "minigun", MetalCost = 200, EnergyCost = 400, }, { Enabled = true, SaveName = "mortar2", MetalCost = 100, EnergyCost = 200, Button = "hud-context-upgrade-mortar2", }, },

  伴随着起航远洋(High Seas)更新,还添加了一个新的升级变量:TransferReloAdprogress。当将其设置为 true 时,升级的武器将保留原来的重新装弹状态。它用于多管甲板炮的扩散和集中射击模式切换。
前置条件
  默认情况下,玩家只要拥有足够的资源就可以建造材料和装置。但是我们也可以添加一个需要建造指定的装置或多个装置的前置条件。这就是原版游戏中使用科技限制建造的实现方式。

  材料和设备(包括武器) 对于前置条件(Prerequisites)变量都具有相同的语法。如果设定为nil(无),则不会限制建造。

  如果指定为字符串,则玩家必须首先已建造此存档名,才能建造这个材料或装置。

Prerequisite = "workshop",

  这是一个设定替代前置科技树的已过时方法,但是最好使用再下面的一个更灵活的语法。在这个例子中,如果玩家建造了战争车间或军械库其中一样装置,则可建造这个项目。

-- 已过时的方法 Prerequisite = "workshop", PrerequisiteAlt = "armoury",

  您也可以设定一个包含多个装置的存档名的复杂集合为前置条件。如果玩家满足任何一种组合,则可以建造这个项目。在这个例子中,如果建造了战争车间和升级中心,或者只建造了弹药厂,即可建造项目。您可以根据需要设定这个集合。

Prerequisite = { { "workshop", "upgrade" }, { "factory" } },

  对于装置的升级项,可以以同样的方式设定它们的前置条件。如果未指定升级项的参数,则默认为建造目标升级项的前置条件。

Upgrades = { { Enabled = true, SaveName = "mortar2", Prerequisite = { { "upgrade" }, { "munitions" } }, MetalCost = 50, EnergyCost = 800, Button = "hud-context-mortar2", -- 设定此参数以自定义关联菜单按钮 }, },
激光束配置
  激光束(Beam)在一段时间内运行,由武器的 BeamDuration 设置时长,因此拥有弹射物(子弹)武器没有的配置项。最简单的设置它们的宽度和伤害是使用 ProjectileThicknessProjectileDamage 变量,它们在 weapons/projectile_list.lua 中,这个文件用于保持常量。激光束的 ProjectileDamage 表示 ‘每秒生命值(hitpoints per second)’。

  另外,激光束可以拥有时间功能,以实现厚度和伤害的变化。这是为了更多引人注目的内容,激光束可以逐渐增加,逐渐降低和振动。要实现它,您可以在您的武器配置脚本中实现 BeamThicknessBeamDamage 函数。它们采用时间值(相对于 FireDelay 的结束),并返回表示激光的瞬时厚度和伤害(每秒生命值)一个值。

  在武器配置脚本中,可以使用 BeamThicknessMultiplierBeamDamageMultiplier 修改常数变量和函数的结果。它们的默认值为1。

  这是等离子激光束武器配置脚本的一部分,我们将其用作示例。为了对激光束进行更直观的调整,我们将数据组织在一个以时间为键的表中,名称为 BeamTable,然后使用 InterpolateTable 来计算中间的值。

dofile("scripts/interpolate.lua") -- 第一列是时间点 -- 第二列是是时间点的厚度 -- 第三列是时间点的伤害 BeamTable = { { 0, 1, 0, }, { 0.25, 3, 0, }, { 0.5, 30, 1000, }, { 1, 30, 1000, }, { 1.5, 0, 0, }, } function BeamThickness(t) return InterpolateTable(BeamTable, t, 2) end function BeamDamage(t) return InterpolateTable(BeamTable, t, 3) end

  随着 2022 年三月的 起航远洋(High Seas) DLC 更新,我们引入了将这些函数指定为子弹项目的一部分的能力。这是必要的,因为弹药(Ammo)系统允许一个武器产生多个激光束。我们还添加了一个新的渲染图,它追踪下面描述的激光束的碰撞点。用横扫轨道激光子弹来作为示例:

table.insert(Projectiles, { SaveName = "ol_beam_sweep", ProjectileType = "beam", ProjectileSprite = path .. "/weapons/media/ol_beam.tga", -- 其他细节已省略 BeamThickness = function(t) return InterpolateTable(BeamTableOrbitalSweep, t, 2) end, BeamDamage = function(t) return InterpolateTable(BeamTableOrbitalSweep, t, 3) end, BeamCollisionThickness = function(t) return InterpolateTable(BeamTableOrbitalSweep, t, 4) end, }) -- 第一列是时间点 -- 第二列是是时间点的厚度 -- 第三列是时间点的伤害 -- 第四列是碰撞渲染图厚度 BeamTableOrbitalSweep = { { 0, 1, 0, 0, }, { 1, 10, 0, 0.25, }, { 1.25, 100, 1500, 1, }, { 3.25, 100, 1500, 1, }, { 4.25, 0, 0, 0, }, }

  警告:将您模组的激光束表命名为独特的名称。使用相同的表名称会覆写之前的值,改变其它激光束的行为。

激光束图层与碰撞渲染图
  在起航远洋之前,激光束只能拥有一个视觉图层。现在可以拥有多个图层,每一个都可以具有自己的厚度函数(thickness functions),滚动速率(scroll rate),图块速率(tile rate)和增加值(additive value)。

  它们也可以只在碰撞点产生效果。我们需要对此进行更多的控制,因此我们添加了一个可选的,拥有自己单独的厚度函数的渲染图。即使它在迅速移动中,它也会完全跟踪激光束的终点。

  每个激光束图层的渲染图都应指定 "repeatS = true,",以使纹理图块沿着激光束的长度。

  使用横扫轨道激光作为示例:

table.insert(Sprites, { Name = "ol_beam", States = { Normal = { Frames = { { texture = path .. "/weapons/media/ol_beam.tga" }, repeatS = true, } }, }, }) -- 为简洁起见,额外的激光束图层定义已省略。 table.insert(Projectiles, { SaveName = "ol_beam_sweep", ProjectileType = "beam", -- 其他细节已省略 ProjectileSprite = path .. "/weapons/media/ol_beam.tga", Beam = { Sprites = { { Sprite = "ol_beam", ThicknessFunction = "BeamThickness", ScrollRate = -2, TileRate = 400*3, }, { Sprite = "ol_beam4", ThicknessFunction = "BeamThickness", ScrollRate = -2, TileRate = 400*3, Additive = true, }, { Sprite = "ol_beam6", ThicknessFunction = "BeamThickness", ScrollRate = -3, TileRate = 800*3, Additive = true, }, { Sprite = "ol_beam2", ThicknessFunction = "BeamThickness", ScrollRate = -6, TileRate = 800*3, Additive = true, }, { Sprite = "ol_beam5", ThicknessFunction = "BeamThickness", ScrollRate = -7, TileRate = 600*3, Additive = true, }, }, CollisionSprite = { Sprite = "ol_collision", ThicknessFunction = "BeamCollisionThickness", Scale = 0.5, Additive = true, }, }, })

最大行程
  起航远洋更新还引入了具有最大行程距离的激光束的概念,该距离在反射和穿过传送门(不增加距离本身)后仍然继续计算。当到达了此距离后,激光束会像光剑一样突然停止。可以通过设置子弹项目的 BeamMaxTravel 变量来调整它。其默认值为一个巨大的数字,所以不会影响到旧版的激光束。

table.insert(Projectiles, { SaveName = "mylightsaber", ProjectileType = "beam", -- 其他细节已省略 BeamMaxTravel = 1000, })
材料转换
  右键单击一个支板时会弹出一个关联菜单,其中包含多个带有材料图标的按钮,这些按钮用于将支板转换为其他的材料。默认情况下,此材料集合设定为原版 building_materials.lua 文件中的DefaultConversions表:

DefaultConversions = { "bracing", "backbracing", "armour", "door", "shield" }

  您可以向修改其它数据一样修改这个集合,但也有另外一种方式,您也可以为特定的材料指定自定义的转换(Conversions)表。您最好将添加的自定义材料与DefaultConversions表中内容合并(Merge),以保留其他模组的修改。要在一个特定的材料的关联菜单中添加传送门的转换按钮,则可以这样:

-- 位于此材料中 Conversions = MergeLists(DefaultConversions, { "portal", }),

  要让此功能生效,添加的材料必须将关联菜单(Context)变量设定为一个已有的渲染图名称,添加按钮渲染图时必须提供一个包含path变量的路径给 ui/uihelper.lua 的ButtonSprite函数,以使它在模组的ui/textures文件夹中找到所需的纹理:

Sprites = { ButtonSprite("context-shield", "context/HUD-Buttons-Shield", nil, nil, nil, nil, path), } -- 位于此材料中 Context = "context-shield",

  您可以参考原版building_materials.lua的"bracing"(实体支撑板)材料作为一个例子,但我们并未定义传送门的关联菜单变量,所以它不会生效。
添加效果和音频
  您可以为您的模组添加自定义的效果,例如爆炸、枪口火焰和弹道。这些可以包含模组新添加的纹理和音效,或者也可以使用游戏已有的资源。引用新增效果时记得使用"path"变量,再加上文件的相对路径,例如在装置脚本中:

DestroyEffect = path .. "/effects/mygun_med_exp.lua"

  要添加新效果,您应该在您模组中名为"effect"(效果)的文件夹中新建一个.lua文件。游戏在加载时会扫描此文件夹并预加载其中的效果。用于效果的资源文件应放于"effects/media"文件夹中,这里面的文件也会被预加载。

  效果脚本一般应使用这样的常规格式:

-- 效果可持续的时间, 若效果构件仍在运行则停止 -- 以秒为单位,超过时间后会移除效果 -- 请确保该值超过与所有效果构件运行的期限 LifeSpan = 3 -- 可选的触发fmod音效事件 (通常基于一个现有的效果脚本) SoundEvent = "effects/mushroom_cloud.lua" -- 渲染图(可以为动画)表. 请注意不要使用重复的名称. Sprites = { { Name = "mygun_medium_explosion", States = { Normal = { Frames = { -- the last frame is blank to prevent looping { texture = path .. "/effects/media/med_exp_01.tga" }, { texture = path .. "/effects/media/med_exp_02.tga" }, { texture = path .. "/effects/media/med_exp_03.tga" }, { texture = path .. "/effects/media/med_exp_03.tga", colour = { 1, 1, 1, 0 }, duration = 2 }, duration = 0.1, blendColour = false, blendCoordinates = false, }, }, }, }, } -- 组成此效果的子效果. Effects = { { Type = "sprite", PlayForEnemy = true, TimeToTrigger = 0, LocalPosition = { x = 0, y = 100, z = 0 }, LocalVelocity = { x = 0, y = 0, z = 0 }, Acceleration = { x = 0, y = 0, z = 0 }, Drag = 0.0, Sprite = "mygun_medium_explosion", -- 在上面定义 Additive = false, TimeToLive = 2, InitialSize = 1.4, ExpansionRate = 0, Angle = 0, AngleMaxDeviation = 0, AngularVelocity = 0, RandomAngularVelocityMagnitude = 0, Colour1 = { 255, 255, 255, 255 }, Colour2 = { 255, 255, 255, 255 }, }, },

  可用的效果类型有:

渲染图(sprite)
  创建一个可以移动、旋转、缩放,由一种颜色渐变为另一种颜色的四边形单纹理效果。即为上面的例子。

声音(sound)
  基于.wav或.mp3文件创建的音效。由于游戏本体可以使用Fmod事件系统进行更多控制,因此游戏本体并不常用。对于玩家创建的模组,这很有用,并允许新增音效文件到游戏中。

{ Type = "sound", TimeToTrigger = 0.0, LocalPosition = { x = 0, y = 0, z = 0 }, Sound = path .. "/effects/media/med_exp.mp3", Volume = 0.6, },

溅射(sparks)
  创建一组由渲染图构成的溅射效果,并根据发射方向确定效果的位置、大小和旋转角度。

{ Type = "sparks", PlayForEnemy = true, TimeToTrigger = 0.1, SparkCount = 12, SparksPerBurst = 12, BurstPeriod = 1, LocalPosition = { x = 0, y = 0 }, Texture = path .. "/effects/media/debris.tga", Gravity = 0981, -- 根据此分布格局放出粒子效果 -- NormalDistribution是可替代的, 此效果为钟型 EvenDistribution = { -- 最小和最大的角度 (例如 -180, 45, 0) Min = -35, Max = 35, -- 每次迭代的标准偏移角度 (设定为零则间隔均匀) StdDev = 5, }, -- 粒子效果的发射角度 -- 取决于这些关键帧插入的属性 Keyframes = { { Angle = -35, RadialOffsetMin = 0, RadialOffsetMax = 20, ScaleMean = 0.5, ScaleStdDev = 0.25, SpeedStretch = 0, SpeedMean = 500, SpeedStdDev = 200, Drag = 1, RotationMean = 45, RotationStdDev = 180, RotationalSpeedMean = 10, RotationalSpeedStdDev = 5, AgeMean = 1, AgeStdDev = 0.5, AlphaKeys = { 0.1, 0.8 }, ScaleKeys = { 0.1, 1 }, }, { Angle = 35, RadialOffsetMin = 0, RadialOffsetMax = 20, ScaleMean = 0.5, ScaleStdDev = 0.25, SpeedStretch = 0, SpeedMean = 500, SpeedStdDev = 200, Drag = 1, RotationMean = -45, RotationStdDev = -180, RotationalSpeedMean = 10, RotationalSpeedStdDev = 5, AgeMean = 1, AgeStdDev = 0.5, AlphaKeys = { 0.1, 0.8 }, ScaleKeys = { 0.1, 1 }, }, }, },

尾迹(trail)
  创建一个跟随效果位置的线性纹理。

{ Type = "trail", Texture = path .. "/media/bullet_trail.tga", LocalPosition = { x = 0, y = 0, z = 9.0 }, Width = 3, Length = 0.75, Keyframes = 100, KeyframePeriod = 0.05, RepeatRate = 0.001, ScrollRate = 0, FattenRate = 0, },

视角摇晃(shake)
  根据镜头与效果之间的距离,使镜头快速晃动。

{ Type = "shake", TimeToTrigger = 0, TimeToLive = 8, Magnitude = 28, },

  每个效果变量都有许多关联的变量。请使用 Forts/data/mods/weapon_pack/effects 目录为依据来学习。您可以拷贝并编辑原脚本文件来制作您的效果。
语言本地化
  在Forts中,所有语言均以模组的方式实现。您可以查看 Forts/data/mods/language-* 目录中的基本字符串表,以了解哪些文件可以修改。要为您模组的需要更改或扩展这些字符串表,您需要修改这些语言模组。再次使用大型沙袋模组作为例子,您可以看到以下文件夹架构(仅展开英语和中文作为示例):

mods/ language-BrazilianPortuguese/ language-Chinese/ devices/ strings.lua mods/ -- 现在这个文件夹没了 commander-bpo-scattershot/ strings.lua language-English/ devices/ strings.lua mods/ -- 现在这个文件夹没了 commander-bpo-scattershot/ strings.lua language-French/ language-German/ language-Hungarian/ language-Italian/ language-Japanese/ language-Korean/ language-Polish/ language-Portuguese/ language-Russian/ language-Spanish/ language-Turkish/

  打开 mods/language-Chinese/devices/strings.lua ,您可以看到:

function Merge(t1, t2) for k, v in pairs(t2) do t1[k] = v end end Merge(Device, { sandbags_large = L"大型沙袋", sandbags_largeTip2 = L"大型沙袋拥有更高的生命值", sandbags_largeTip3 = L"需要:升级中心", })

  这为我们示范了如何合并描述新装置的字符串到子叫做装置的字符串(sub-string)表中。字符串ID(例如"sandbags_large"和"sandbags_largeTip2")是根据每个设备的存档名自动生成的,之后游戏会从这里查找要显示的字符串。

  如果要替换已有的字符串,您可以直接覆盖它。以散射大师指挥官的 string.lua 为例子:

Scattershot.ToolTipLine2 = L"被动技能: 糟糕了,加农炮只能造成溅射伤害。" Scattershot.ToolTipLine3 = L"主动技能: 试试看吧。"

  如果对于特定的语言缺少了特定的字符串ID,则提示会显示为空白。但在大多数情况下,游戏会显示字符串ID作为替代,这有助于您寻找缺少翻译的字符串ID。
依赖目标月球的特性
  目标月球(Moonshot) DLC 加入了一些原版游戏没有的特性。如果您需要使用这些特性,则您的模组将依赖于目标月球,并且只有拥有目标月球DLC的主机才能激活您的模组(在你自己一个人测试的时候就是你自己)。在选择模组界面中,这些模组会显示一个蓝色的目标月球图标。

  制作依赖于目标月球的模组时,您必须在 mod.lua 文件中添加一个标记(flag),以便游戏可以快速地识别出它。您可能会收到一个错误信息,如果您尝试上传一个依赖于目标月球的模组,但又未添加此标志:

RequiresMoonshot = true

  在下面将为您概述一些会触发依赖性的特性以及与其相关的关键词。请注意,如果这些关键词出现在相关文件中的任何位置,即使它们的位置不恰当或在注释中,也会使模组成为目标月球依赖项。

支持目标月球
  如果您想在目标月球可用时使用它的特性,但是如果目标月球不可用时,仍然允许您的模组在它不用做的情况下运行,那就改用这个标志:

SupportsMoonshot = true

  游戏会自动设定两个变量,dlc1Var_Activedlc1Var_Value 。当目标月球可用时,它们会依次被设置为 true1 ,如果目标月球不可用,则设置为 false0 。您可以使用它们来防止添加没有意义的武器。

传送门(Portal)
  在 building_materials.lua 文件中的关键词Portal会触发依赖项。此特性允许玩家连接两个支板,并将子弹与激光在这两个支板之间传送。

最多发射次数(MaxBursts)
  在武器脚本文件中或 weapon_list.lua 文件中的关键词MaxBursts会触发依赖项。此特性允许定义于武器脚本文件(例如machinegun.lua),武器在射击指定多的次数后会自毁。此特性用于目标月球的武器电锯。

线路伤害(RayDamage)
  在 projectile_list.lua 文件中的关键词RayDamage会触发依赖项。当子弹与支撑板碰撞时,此特性可结合一些其它参数,用于在直线上造成伤害。例如电锯的子弹:

RayOffset = 0.1, RayLength = 50.1, RayDamage = 7.5, RayDamageLimit = 3000, RayIncendiaryRadius = 0, RayExcludeBackground = false, RayStopAtForeground = false,

范围半径(FieldRadius)
 在 projectile_list.lua 文件中的关键词FieldRadius会触发依赖项。它可以与一些变量共同使用,以创建用于各种目的的范围。此特性用于实现烟雾弹和吸引子弹到磁力射线的打击点。例如烟雾弹的子弹:

FieldRadius = 200.0, FieldType = FIELD_DISRUPT_BUILDING | FIELD_BLOCK_BEAMS | FIELD_SCRAMBLE_GUIDANCE | FIELD_BLOCK_FIRING, FieldIntersectionNearest = true, FieldStrengthMax = 1.0, FieldStrengthFalloffPower = 1.0, MagneticModifierFriendly = 0.0, CollidesWithBeams = true,

  范围类型(FieldType)的可选设定有:

FIELD_DISRUPT_BUILDING FIELD_MAGNETIC FIELD_BLOCK_BEAMS FIELD_SCRAMBLE_GUIDANCE FIELD_BLOCK_FIRING
动态脚本
  您可以在模组根目录创建script.lua文件,模组会根据游戏触发的事件执行任何Lua脚本,这些事件包括子弹撞击、装置和武器的毁坏、所有权变化和玩家输入,初始加载、物理帧、显示帧和重新开始游戏。您也可以安排函数在指定的时间后执行。它们的行为与地图脚本任务文件相同,但不与地图捆绑。

  通过响应这些事件,模组脚本可以对游戏状态进行一系列修改,创建效果或在屏幕上显示控件。它还可以在"data"表中跟踪其期望的任何状态。这个表会自动进行序列化,以重播和加入游戏战斗之中。

  关于动态脚本的详情可以在 Forts 脚本 API 资料页[www.earthworkgames.com] 页面查看,该页面直接根据Forts源代码的标注和提供描述自动生成。
模组类型
  为使您的模组更容易在 Steam创意工坊 和游戏的选择模组界面中寻找,我们建议您设定模组的类型(Category)。这是一个模组根目录的 mod.lua 文件中的可选参数。它可以在创意工坊为项目添加标签,方便一些玩家闲的没事时可以搜索到您的模组。在游戏的选择界面中,它可以将您的模组放入一个模组类型分支中,玩家可以折叠分支以便快速浏览。

  您可以像这样在 mod.lua 中添加此参数:

Category = "Weapons"

  如果缺失 mod.lua 、未指定类型变量或定义了无效的类型名,则游戏会将此模组置于杂项类别中。有效的类型有:

  • Combat (战斗)
  • Devices (装置)
  • Physics (物理)
  • Resources (资源)
  • Rules (规则)
  • Misc (杂项)
  • Disable (禁用)
  • Weapons (武器)
  • Technology (科技)
  • Materials (材料)
  • Environments (环境)
  • Campaigns (战役)
测试与发布
  只要您对模组在本地的运行效果满意后,您就可以考虑将其上传到 Steam创意工坊 了。在执行此操作前,您需要在模组的根目录创建一个 preview.jpg 文件,该文件用于在 Forts 和 Steam创意工坊 中显示。图片分辨率大小影响不大,只需要将长宽比固定为 16:9,从而让其在 Forts 中正常显示。地图的预览图为 1024*576,模组预览图也可使用该分辨率。

  要发布模组,在游戏主菜单中点击创意工坊。在"上传"标签中选择您一直致力于制作的模组。点击上传,然后选择是。稍等片刻,游戏会在内置浏览器中打开创意工坊项目页面。正常情况游戏会自动订阅该模组。如果需要测试,您应该将其可见性设定为隐藏或仅限好友。为确保模组也能够在其他人的游戏中正常运行,您应该让好友订阅它并进行游戏,您也可以在游戏中选择带有 Steam创意工坊 图标的版本进行游戏以测试。从中您可以发现到所有开发模组过程中疏漏的资源路径问。修复后,在创意工坊界面中更新你的模组,并设定可见性为公开。
在地图中强制启用模组
  与地图内置模组相同,您也可以在自定义地图中强制启用创意工坊上的模组。在您地图的任务脚本文件中,添加创意工坊ID到Mods表以强制启用创意工坊模组。例如,在地图中强制启用大型沙袋模组:

Mods = { "1293804859" }

  要使其获得更高可读性,您可以这样写:

sandbags_large = "1293804859" Mods = { sandbags_large }

  如果您的地图在开局时会使用到模组所添加的内容,添加此参数至关重要。



     至此,您已阅读完毕所有教程。祝您制作出更加优秀的模组。


15 Comments
萌新肉丝Rothes  [author] 22 Apr @ 5:07am 
API中有弹射物命中事件。
fortune00003 22 Apr @ 3:51am 
大佬有知道如何获取有关武器造成的损害的信息吗?哪里会有实时记录此信息的日志文件?
布川库子AF 4 Dec, 2024 @ 3:36am 
我逐渐理解一切...
sgzx 26 Mar, 2024 @ 3:30pm 
谢谢教程
Emiya 3 Jan, 2024 @ 7:48pm 
1
f$djv 18 Feb, 2023 @ 7:25pm 
感谢
萌新肉丝Rothes  [author] 18 Feb, 2023 @ 7:39am 
物理变量在 Forts\data\db\constants.lua 的 Physics 表中
萌新肉丝Rothes  [author] 18 Feb, 2023 @ 7:37am 
乱码把文件的编码改为UTF-16 LE
f$djv 18 Feb, 2023 @ 4:34am 
为什么DisplayName =
{
["English"] = L"English",
["Chinese"] = L"中文",
}
这样写中文显示的是乱码
f$djv 18 Feb, 2023 @ 3:29am 
大佬知不知道原版的物理变量有哪些啊:steamthumbsup: