【物竞天择2】NS2-MOD 制作指南
本文意指向更多对物竞天择2或技术感兴趣,
并乐于为社区开发做贡献的人提供最基础的帮助,
更多详情或问题咨询可以进Q群:540422237了解,群内会提供更细致的帮助。
入门
Brute's Modding Guide 吃下这篇指南以掌握基本MOD的开发流程.
进阶
以下内容多数需要翻墙,请自备梯子跟英文翻译.
音效添加:Rio的MOD创作帖
地图制作:Natural Selection 2 Mapping Tutorial
Cinematic(动画/特效)制作:Natural Selection 2 Cinematic Tutorial
NS2CN社区正在运行的开源MOD.StriteR's Github (NS2开头仓库)
施工中
原文:Ghouls Modding Guide
这是一个旧的草案,有些细节可能已经过时了!
Spark Engine (火花引擎)
在开始制作NS2mod之前,必须先看一下它所使用的引擎。对于NS2来说,它使用了由UWE自研的Spark火花引擎。
关于Spark引擎有两件重要的事情需要理解。首先是它基本的工作原理,其次是它是如何挂载文件的。
工作原理
火花引擎Spark(以下简称火花)包含了很多不同的模块。我现在只描述最重要的那些,这样你就可以基本知道火花是如何工作的。
理解这一点很重要,因为不理解这个那开始修改制作mod将是毫无意义的。
基本上所有的NS2游戏代码都是用Lua编写的,并由引擎通过Lua模块来执行。
Lua代码本身现在也可以在不同的Lua虚拟机(vm)中运行,但我稍后将更详细地解释这一点。
基本上,火花通过网络模块使用网络包与客户端/服务器进行通信。
如果网络模块接收到一个包,它将在Lua模块上调用一个事件,该模块调用负责处理该包的所有代码函数,并根据它来决定应该发生什么。
除此之外,还有一些我刚才调用的核心模块。基本上,它是用来管理本地按键输入和相关的调用,如网络模块Lua模块的事件,从而调用Lua 虚拟机中的某些注册函数,根据输入生成输出,如按下M键换图→ 呼出事件“按下M键”→ 在客户端虚拟机上调用Lua函数KeyDown(Key)按下M键→ 玩家在游戏中游玩→ 显示地图→ 输出到渲染模块:显示贴图实体(出现地图选择栏)
在输入给定的模块时,通过事件调用某些Lua函数,该函数根据输入生成一个特定的输出,这些输出被传递到输出模块(渲染,网络等。)
基本上就是这样运作的。
如何装载文件
通常,火花会在装载文件时检查该给定的文件是否已挂载。
如果是,它将卸载旧文件并挂载新文件。
否则,它只会安装最新的个。
例如:
我们的mod包中有lua/Alien.lua。现在spark注意到它已经装入了lua/Alien.lua,因为这是一个普通的文件。所以它会卸载普通文件,而装载我们的修改过的mod文件。
这使得有两种最基础的方法让修改NS2文件成为可能:
①. 替换普通文件:
您可以通过很基础地替换普通文件,来添加你修改过的文件,并将它们添加到mod列表中。因为spark将用修改过的mod文件替换掉普通文件。
但这可能会导致两个问题:
其他mod的同名文件可能会替换掉你已经被修改后的mod文件。
所以,仅仅替换文件很容易导致两个插件冲突
如果在普通文件中添加了一些对其他游戏代码很重要的东西,会发生什么呢?因为你基于旧的ns2构建的修改文件不包含新的东西,那么你的mod将导致ns2奔溃,直到你更新你的mod。
②. 添加新文件:
这样你就可以为你的mod创建属于自己的文件,这样就不会卸载任何其他文件,你的mod文件也不会被其他任何mod卸载。但问题是如何链接你的mod文件被普通代码执行?
我将在游戏代码修改部分回答这些问题。现在让我说,如果你正确使用了文件夹结构,映射器就根本不会有链接问题,因为ns2会让所有映射都可以自动选择:)这就引出了我们的下一点。
普通文件夹结构
NS2使用了一种特定的文件夹结构,我将在这里简要地通过在下面的列表中展示它,并在“-”后面解释每个文件夹的用途:
• Lua 包含所有游戏代码文件(.lua 文件)
• Model 模型 — 包含游戏使用的所有模型(.model 模型文件 .material 材质文件和.dds 贴图文件)
• Map 映射 — 包含所有映射文件(.level 文件)
• Material 材质 — 贴图所使用的材质和贴图(.material 材质文件和 .dds 贴图文Sound 声音 — 包含游戏使用的所有声音文件(.fsb 事件索引文件 .fev音频数据文件和.soundinfo 音效文件)
• Ui — 包含UI使用的所有纹理(.dds 纹理贴图文件)
• Screen 屏幕 — 包含用于加载屏幕背景的所有图像(.jpg 图片文件)
• Shaders 着色器 — 包含游戏使用的所有着色器(.fx 高阶着色器文件 .screenfx 屏幕着色器文件 .hlsl 高阶着色器源代码文件 和.Shader 着色器文件)
• Cinematic 电影动画 — 包含游戏使用的所有动画(.cinematic 和 .dds 纹理文件)
• Gamestrings 游戏字符串 — 包含用于翻译游戏文本的文本文件(.txt 文本文件)
ns2使用的普通文件可以在ns2安装文件夹下的ns2和“ns2核心文件”中找到。提示:如何打开ns2安装文件夹:


右键单击Steam库→右键natural selection 2中的管理→浏览本地文件
火花引擎登录器


1. 新建-创建一个新的mod文件夹
2. 打开-打开一个现有的mod文件夹
3. 配置-配置当前打开的mod
4. 发布-发布当前打开的模型
5. 源文件-打开当前打开的mod文件夹的源文件夹
6. 输出-打开当前打开的mod文件夹的输出文件夹
7. 生成器-将源文件夹的所有源文件转换为ns2使用的格式,并将它们保存到输出文件夹中
8. 启动游戏-启动游戏与输出文件夹的所有文件的安装。
9. 地图编辑器-打开地图编辑器。
10. 查看器-打开模型查看器。
11. 动画编辑器-打开动画编辑器。
12. Decoda-打开随ns2附带的LuaIDE(Lua编译器)
13. 编辑器手册-打开地图编辑器的手册。
14. 地图指南-打开ns2普通地图设计指南。
15. 示例-打开ns2资产文件夹,其中包括ns2附带的所有资产(模型源文件等)。
测试mod
至少运行一次生成器以构建源文件夹。
然后点击启动板中的“启动游戏”,用你的mod安装开始游戏。然后通过服务器浏览器→创建启动一个本地服务器


习惯使用ns2的作弊模式指令和控制台调试指令来复原某些情况并使用机器人测试mod是非常必要的。
您可以在这里找到所有可用的ns2控制台命令的更新列表:http://wiki.unknownworlds.com/ns2/Console_Commands
然而,对于一个真正的玩家测试,你无法避免的需要发布你的mod,并在一个专门的服务器上测试它们。
发布模块
在发布之前,请确保你的mod在“configure 配置”下有如下图的标题和描述,[图像编号]待添加
否则就不可能从创意工坊安装你的mod了。在您检查没问题之后只需点击发布按键就可以发布mod到创意工坊了。
更新一个mod的方式也是一样的。
错误警告:如果你点击发布,显示说“发布成功”,但登录器只把你的publish_id(发布id)从 mod.settings (模组配置)文件中删除。
在这种情况下,只需打开mod.settings 文件(您可以在mod的父文件夹中找到它)并添加
publish_id=“[modid]” //赋值一个发布id和mod id
提示:手动设置publish_id还允许你再次上传和更新旧的创意工坊mod,为此你可能会删除mod文件夹。
提示:根据创意工坊链接获取mod Id。
如果你忘记了你的modid再点击创意工坊链接可以很容易找到。
获取到你的mod的创意工坊页面的链接,然后复制链接中“id=”后面的数字。进入谷歌,输入将[从链接获得的数字ID]转码为十六进制。谷歌呈现给你的十六位数字就是十六位的modid(忽略0x前缀
地图编辑器
作为地图编辑器,登录器面板上的地图编辑器将是您使用的唯一工具。要适应地图编辑器的UI界面,请阅读游戏根目录下的地图编辑器手册,还有地图设计参数的一些标准。
为了开始使用ns2地图编辑器,我推荐mendasp的这个教程视频:http://youtu.be/oIyS0Z7CJFY?list=PLH0GoQmA4BGUbl0UxgR65XPAcDO0O_jSu

https://www.bilibili.com/video/BV1v3411i73k/?spm_id_from=333.999.0.0
基本上,一个很好的教程列表可以在这里找:http://www.youtube.com/watch?v=oIyS0Z7CJFY&index=1&list=PLcQ9D-WW8z7EmVgv2lIokfciBLqQhF2Xi
总的来说,我建议以ns2地图制作者的身份加入SCC组,因为它提供了一个很好的问题收集反馈,并如果你被卡在某个地方可以去官方Discord上寻求帮助。
动画电影
注意:这有点像仅仅使用动画编辑器的mod。我自己从来没有真正使用过它,所以希望能找到其他人能补充这个话题。
建模
NS2支持max和fbx文件。Blender文件也将很快被支持添加
可以学习3DMAX.搅拌机的插件可以在论坛里获得
纹理模块
物竞天择2使用DDS来存储它的纹理。所以,如果你想打开一个纹理来编辑它,你将需要一个能够打开DDS文件的软件。
然而,对于新纹理的最佳做法是将它们保存为PSD文件,并让构建器将它们转换为DDS。
您可以使用的软件:
• Paint.net (http://www.getpaint.net/)(对于PSD支持:https://psdplugin.codeplex.com/)
• gimp (http://www.gimp.org/)(对于对DDS的支持:https://code.google.com/p/gimp-dds/)
• (为DDS支持:下载免费的Nvidea纹理工具https://developer.nvidia.com/nvidia-texture-tools-adobe-photoshop)

https://www.bilibili.com/video/BV1SA411F7Q3/?spm_id_from=333.999.0.0
音效模块
[更新里约热内卢的声音图:http://forums.unknownworlds.com/discussion/comment/2076854/#Comment_2076854使用最新的fmod软件]
着色器模块
注意:NS2使用HLSL着色器,所以你应该在互联网上找到足够的指南来开始。
游戏代码模块
首先,你需要使用Lua来修改ns2的游戏代码。
在本教程中,我不会解释Lua!所以如果你不习惯Lua,我建议你读这篇文章免费的Lua书(http://www.lua.org/pil/contents.html),然后你继续阅读本指南。
Spark还使用多个Lua虚拟机,所以在我们开始修改之前,了解这些虚拟机和它们做什么是很重要的。
LUA IDE(Lua语言编译器)
如果您不确定要使用什么IDE,这里有一个简短的列表(按受欢迎程度排序):
● Intellij IDE(有EmmyLua 插件)
● VSCode (带有vscode-lua or emmylua 插件)
● 记事本++ (带有 LuaC & GLua 插件)
● Atom(带有language-lua, 或linter & linter-glualint 插件)
● Decoda IDE(包含在NS2火花引擎登录器中)
● ZeroBrainStudio (特别是这个分支: https://github.com/sclark39/ZeroBraneStudio/tree/master)
● 带有lua插件的Eclipse
● Sublime
● ...
LUA 虚拟机VMs
客户端虚拟机,它将运行客户端代码和客户端上的共享代码
共享虚拟机VM-VM,它将比对预测服务器端与客户端的操作,以平滑网络延迟。
服务器端虚拟机,它将运行服务器端代码和服务器上的共享代码
加载正在加载屏幕的虚拟机。预缓存文件和启动其他虚拟机。为主菜单加载的主菜单(当未连接到服务器时)
提示:此外,少量的UI元素(比如武器的弹药计数器)也可以在它们各自的虚拟机vm中运行。
提示:服务器端的权限为最高总是高于用于预测的共享虚拟机。因此,客户端会尽快纠正错误的预测。
服务器和客户端虚拟机通过网络消息相互通信,如前面关于Spark引擎的部分所述。
我们现在将忽略共享虚拟机VM,因为你只需要看它更高级的东西。
在这一点上,你做了一些需要被预测的事情,你将如此习惯于游戏代码,你不再需要任何指导了:)
这意味着我们基本上有3个不同的Lua文件:
- 客户端:Lua文件有一个“_Client”附录。这些文件将在客户端虚拟机VM上加载
- 服务器:Lua文件有一个“_Server”附录。这些文件将在服务器虚拟机VM上加载。
- 共享:所有没有附录的Lua文件都是普通共享文件,将加载到客户端和服务器端两个虚拟机中
请注意,您不能访问没有在同一虚拟机VM中声明的变量、对象或函数,除非它们已联网。
写代码
编码步骤:
1. 首先要知道你想做什么。写下你的mod的简短概念。
2. 查找您将需要修改的普通类、方法、变量和文件。
3. 开始编码(逐步进行)
4. 测试您的mod并调试它
为了在普通类中查找东西,我强烈推荐ns2文档。您可以根据这个简短的指南生成您自己的ns2文档:http://forums.unknownworlds.com/discussion/112360/ns2docs/p1
或者只是使用我维护的ns2文档镜像:http://ns2docs.bplaced.net/
否则,只能使用IDE的搜索文件函数来查找在普通代码中如何使用方法和变量。
也要记住引擎功能,它的文档可以在/docs文件夹的ns2安装文件夹中找到(.json格式)。
结合这些事情,你应该很快就知道必须修改那些文件才能实现你想要的功能了。
如果你仍然不能实现你的代码功能,可能是由于在普通的代码中有的东西是隐藏的,可以去discord上寻求修改部分隐藏代码的帮助。
当你概念化你的mod时,你也必须考虑如何将你的mod集成到普通文件中。
我将在本指南中介绍4种让mod运行的方法。你所能接受到的是两种方法,你可以修改添加完整的ns2文件或重载这些文件。
我现在将向您介绍基于它们所代表的什么类型的4种修改方式:
• 文件替换
◦ 只是替换和修改普通的文件
这是实现ns2mod最简单的方法,因为你所要做的就是将给定的普通文件复制到mod文件夹中,并按照你喜欢的方式修改它。
但是这样做,您将遇到与我已经提到的被相同文件名替换的问题。
◦ game_setup.xml (游戏配置拓展文件):
该文件定义了每个LuaVM 虚拟机的起点,这意味着它定义了在启动LuaVM虚拟机时加载了哪个Lua文件。通过将这些条目更改为您自己的文件,您可以完全修改spark火花引擎mod的加载顺序。
这意味着您只需替换一个文件就可以创建一个完整的mod。基本上,如果你想创造一个游戏,这就是正确的选择。
提示:game_setup.xml还定义了在服务器列表中显示的游戏模式名称。
• 文件添加:
◦ 进入系统:
通过编写引导文件,您可以在所有普通文件都已经完全加载后,再将设置文件加载到每个VM虚拟机中。
这允许您轻松地加载mod,并通过钩子和表项覆盖轻松地修改函数、变量和类。
欲了解更多细节,请查看Modloader Gdoc
◦ Shine插件:
由许多ns2服务器运行的ShineMod基本上是一个框架,它通过插件提供了其他更加灵活的功能,可以由服务器管理员动态地启用或禁用。
Shine会自动加载安装在lua/Shine/扩展文件夹中的所有插件。
因此,使用shine核心框架,您可以通过编写shine插件轻松扩展ns2
这样做的好处是,您可以使用完整的shine框架和所有的shine库,这允许您更快地写功能,而无需考虑如何让功能运行。
更多的细节可以在这里找到:https://github.com/Person8880/Shine/wiki#for-developers
引擎事件钩
除了连接到lua函数之外,您还可以通过事件向某些引擎事件添加钩子。钩子(字符串钩子名称,函数钩子函数)是创建mod的最重要的引擎特性之一。
下面是最常用的引擎挂钩的一个简短的列表:
• 服务器虚拟机事件
◦ “客户端连接”-呼叫连接连接的客户端
◦ “客户端断开连接”-调用连接与断开连接的客户端
◦ “加载地图后”-在完成加载地图后调用钩子
◦ “加载地图前”-在开始加载地图之前调用钩子
◦ “映射实体”-在映射实体时使用类名、组名和值
◦ “更新服务器”-调用连接每个服务器的提示符
• 客户端虚拟机事件
◦ “更新客户端”-呼叫连接每个客户端提示
◦ “加载地图后”-在完成加载地图后调用钩子
◦ “客户端断开连接”——在客户端与服务器断开连接后调用钩子
网络消息
由于您不能从客户端对服务器端的变量进行访问,而另一方面,您必须共享两者都需要的值,对吧?这是通过网络消息来完成的。
您可以使用Shared设置网络消息。注册寄存器网络消息(字符串消息名称,表参数)
在参数表中,您必须为每个表条目定义一个类型,例如:
本地参数={
间隔1=“整数(0到10)”,
浮动1=“浮点(0到1乘0.05)”,
字符串1=“字符串(255)”,
布尔1=“布尔”
}
发送您在客户端和服务器端上使用的网络消息。发送网络消息(客户端目标、字符串消息名称、表数据、布尔可靠)(可靠定义服务器是否应确保客户端收到消息)和客户端客户端。发送网络消息(字符串消息名称、表数据)。
如果您想向服务器上的所有客户端发送消息,您可以简单地使用
服务器。发送网络消息(字符串消息名称、表数据、布尔值的可靠性)。
对于接收网络消息,我们再次使用引擎钩子函数,称为服务器。连接网络消息(字符串消息名称、函数连接函数(客户端、消息表))或客户端。连接网络消息(字符串消息名,函数连接函数(客户端,消息表))
控制台命令
您可能希望使用控制台命令。这些也只是另一个引擎事件钩子
假设你想钩子到sv_hello输出“你好”,你所需要做的就是使用事件。钩(“Console_sv_hello”,函数命令([客户端],arg1、arg2、...))
Let’s say you want to hook into sv_hello to print out “hello” all you need to do is use Event.Hook(“Console_sv_hello”, function Command( [Client], arg1, arg2, … ) )
客户端只在服务器上传递,传递的参数是在命令后面以空格分隔而给出的字符串。
混合器
它们在ns2代码中起着核心作用,并且是独特的SparkLua结构,这使得它们一开始很难理解。我经常称它们为“已实现的接口”,因为这基本上是它们的设计智慧。每个Mixin都可以扩展给定类的功能,而不干扰类方法或其他混合方法。
有时唯一的缺点是,如果两个混合物都被附加到同一个类上,你就不能正确地说出使用相同名称的mixin方法。当然,这也可以用于侦听某些方法的调用。
关于这个话题,非常有趣的是下面的视频, Dushan 试图独自进一步解释混合器的概念:https://www.youtube.com/watch?v=EQhlp6rCrY8
示例代码项目“Zombie”
好吧,目前这样的理论已经足够了。我现在将使用这个小mod项目向您展示编码mod是如何工作的。我会用我之前给你展示的所有mod方式来实现这个mod。
在我们开始之前,我们需要一个关于我们的小mod的概念。
我决定为这个教程制作一个小的僵尸模式mod。
基本游戏玩法:
异形作为僵尸,每个陆战队员如果被僵尸杀死都会变成僵尸。
在还没开始的时候,玩家只能加入陆战队,如果有超过一定数量的陆战队玩家加入,我们就会选择一定数量的玩家来扮演僵尸。
随着游戏的开始,我们将建好地图上的所有电源节点,如果一个电源节点被摧毁(没有应急灯),我们将把房间完全弄黑
游戏将在一定时间内如果陆战队没有全部变成僵尸则陆战队员获胜,否则如所有陆战队员都被变成僵尸,则僵尸获胜
如果一个僵尸被杀死,它会在一段短暂的延迟后重生。
个人资源点=均衡分数点
僵尸应该在开始的时候就比陆战队员更强大一些。僵尸会根据陆战队的死亡而获得分数
陆战队的生存时间和僵尸死亡都可以获得分数。
陆战队应该能够按B键打开购买菜单以购买新武器。
这个概念还不够完美,但它应该足以编写一个基本的ns2mod。
所以,让我们来看看我们将需要修改或改变什么。
因为这将是一种游戏模式的mod,我们将无法在不改变NS2游戏玩家类的情况下创建它,它基本上管理着所有的游戏回合逻辑。
所以我们需要改变检查回合开始和结束的函数。查看ns2游戏子项的ns2文档页面
http://ns2docs.bplaced.net/ns2docs/tables/NS2Gamerules/index.html我们可以很容易地发现NS2游戏:检查启动()和NS2游戏:检查启动()
we can easily spot NS2Gamerules:CheckGameStart() and NS2Gamerules:CheckGameEnd()
接下来,我们需要避免任何人加入异形队伍,而不是直接把他们和我们的mod放在那里。
如果你再看NS2游戏的ns2文档页面,你会看到它也通过NS2游戏处理团队加入(玩家,加入的队伍编号,强制)。
If you look again at the ns2docs page of NS2Gamerules you see that it also handles team joins via NS2Gamerules:JoinTeam(player, newTeamNumber, force).
游戏开始的代码会隐藏在NS2游戏玩家类中。有一个NS2游戏规则:SetGame启动了(),但正如它之前的评论所说,它似乎只是为了测试的原因才存在。
The game start code is a bit hidden in the NS2Gamerules class. There is a NS2Gamerules:SetGameStarted() but as the comment in before it say it seems to be only there for test reasons.
但是,即使这个函数没有在普通的ns2中被使用,它也会帮助我们,如果我们看看它调用ns2玩家的代码:Set游戏状态(k游戏状态。已启动)。
But even if that function doesn't get used in vanilla ns2 it helps us as we see if we look at the code that it calls NS2Gamerules:SetGameState( kGameState.Started).
看看这个方法,我们找到了一个关于k游戏状态的如果检查。开始,其中包括“真正的”开始游戏呼叫。所以我们需要添加我们的开始游戏的内容。
Looking at that method we find an if check for kGameState.Started which includes the “real” start game calls. So we need to add there our Start Game stuff.
我们现在如何把一个陆战队员变成一个异形?只是把死后的陆战队员放进了异形的队伍。让我们来看看人类页面的子项有什么http://ns2docs.bplaced.net/ns2docs/tables/Marine/index.html
哦,找到了“kill”指令,向我们展示了有一个陆战队员:OnKill(攻击者,行动者,指向,方向)的方法。完美!
Oh look searching for “kill” shows us that there is a Marine:OnKill(attacker, doer, point, direction) method. Perfect!
这已经足够了,现在我们可以在做剩下的事情之前先从核心内容开始了。
That's enough by now we can start with the core stuff before doing the rest.
译:祖莉ZulyHiroky 校准、编辑:StriteR.