欢迎来到我的都市天际线Mod开发系列教程第二期!首先让我们看看这期主要开发的模组效果。
广告牌不见了吗?这就是一个能够移除广告牌的模组。让我们再看一遍。这里原本有广告牌对吧?如果安装了这个模组之后,就可以将这些广告牌移除掉。这就是我们这个模组的主要功能。接下来我们要怎样编写这段代码呢?让我们来看看这个教程。
让我们看一下第一步是什么?实际上第一步就是建立这个项目,设置好项目。这是关键的第一步。此时,我们必须打开我们的开发工具——Visual Studio。然后点击创建新项目。根据上期教程我们学到的内容,当我们创建新项目时,选择C#,然后选择Windows下的“类库”,即Library。接着选择输出为DLL格式,用于创建C#的类库。选择DLL项目,即动态连接库项目。然后点击下一步,在配置新项目时,为项目命名。按照教程指导一步步进行。注意文档中给出的名称,复制粘贴过来,确保正确输入。在选择位置时,放在你的文档底下,例如Future Studio或2019,如果是2017也可以。选择文件夹后,点击创建即可。
点击创建按钮,即可建立一个新项目。好,我们现在进入Visual Studio为我们创建的项目编辑器页面。首先,我建议将"Class1"这部分改成一个更具描述性的名称,比如"Mod"。这样以后如果我们有很多C#文件时,就会更容易找到。我们找到这个"Mod",它是我们主要的起始文档。在这个起始文档里,按照给出的代码,复制粘贴即可。
然而,一旦我们复制粘贴,可能会遇到问题,显示红色波浪线。这表示找不到某个类型或命名空间。这时候我们需要将动态连接库(DLL)提供的都市天际线链接起来。这在第一集中也有提及。我们需要添加引用,右键点击项目,选择“添加引用”,浏览找到都市天际线这个游戏安装目录根目录下的DLL文件,并加入应用程序中。建议大家将常用位置和指令写在一个纯文字文档中,这样每次选择会更轻松。你可以先把这些内容写在文档中,然后在创建项目时,按照文档内容进行复制粘贴操作,这样能够快速且准确地完成整个过程。
那么最好的做法是这样的,实际上,有时候我们会用到一些UI界面。所以更完整地按照步骤选择确定。在这个阶段,一个神奇的事情发生了。我之前红色波浪线的报错消失了。这表示我们已经成功引用了i city这个命名空间或类,也就是动态连接库的内容已经连接成功,因此红色错误部分消失了。接下来让我们稍微解释一下代码的意思。Namespace表示命名空间,我们将这个模组命名为"tBeautiful",即一个让城市更美化的模组。在其下一层级是Class,显然Namespace的下一个就是Class。
Class代表类,我们将其命名为"tBeautifulMod",它继承自"iUserMod"。而"iUserMod"是从哪里来的呢?实际上"iUserMod"就是从"iCity"这个引用中提供的一个可以继承的接口。接口通常用"I"来表示,表示接口。你可以点击查看,它显示这是一个接口。这个接口实际上来自都市天际线开发人员所提供的程序集,可以提到程序集"iCityVersion1000"等等。我们要继承这个接口,然后开始进行开发。当继承这个接口后,就需要去实现这个接口,也就是要去实际做这个接口定义的工作。在实现这个接口时,必须提供两个信息:一个是名称N,即你的模组名称,另一个是Description,描述一下这个模组的用途。我们在上一期中介绍过这点,简单来说这两者是必须要提供的信息。在选择模组时,这些信息会显示出来,并在游戏主菜单中显示。因此,这些信息是必须要输入的。
如果想要更仔细的话,我们可以在这里输入版本号,比如1.0版,这样更容易识别。因为随着时间推移,版本可能会慢慢更新。这个是开发每个模组都最基本的要求,就是对模组进行声明,即了解模组的名称和用途,并做一个简单描述。让我们继续跟着教程往下走吧。
关于C#语言的语法,我就无法详细说明了,因为限于篇幅。如果有不理解的地方,可以去搜索B站上许多关于Unity和C#入门教程,大家可以自行学习一下,由于篇幅问题,我无法深入讲解,大家可以自己延伸学习一下。
好,第二步是什么?
Loading hook。
Hook指的是使用我们自己的方式来复写原有提供的方法,也可以翻译成“钩子”,就是将其勾住,然后用我们的代码来改造它,或者说用我们的方式去修改原有内容。
关于城市模组的API,它包含了各种接口,可以继
承这些接口,创建类来实现这些接口,从程序员的角度来看,我们通常将其翻译为“接口”。
你可以创建类来改进这些接口,以便与游戏进行连接。
其中最重要的接口是loading extension,加载扩展和扩展和类的改进。这个接口在加载过程开始、完成以及安全卸载时会收到通知,是最重要的接口。
当我们实现了这个接口后,在游戏的载入过程中,我们的模组就会被载入,并可以显示特定信息或者设置参数等。简单来说,实现了这个接口后,我们可以在模组加载后执行我们在代码块中编写的内容。例如,看一下代码块"on level loaded",意味着当游戏载入时,也就是城市地图被载入时,会执行什么动作。这里告诉我们要执行的动作是输出一个面板,称为"exception panel",有点像一个异常或警告面板,然后我们可以设置面板上显示的消息,标题是"City Beautiful",内容则是"the level is loaded",表示城市地图已经载入完成。
因此,每个模组在初始化时,首要做的事情就是通过代码块"on level loaded"来实现它的功能。让我们先创建这段代码,但根据所提到的,我们需要先创建一个名为"city beautiful loading.cs"的文档。可以复制这个名称,右键点击添加新建项,选择C#类,并按照刚刚复制的名称粘贴为"CityBeautifulLoading.cs",然后添加即可成功创建这个文档。
在我们的解决方案中,有一个包含默认内容的文档,这是Visual Studio默认生成的内容。在这里,它会帮助我们命名"NextSpace"命名空间,并将它写入其中。为什呢?因为项目中存在两个.cs文件,这两个文件都属于"CTBeautiful"命名空间,所以它会智能地将它们放在同一个命名空间下。这种自动化的处理可以帮助我们节省时间。
关于类的部分,它会根据你给出的答案名称,比如"CityBeautifulLoading",自动为你设置类的名称,这样的设计非常方便,省去了手动输入的步骤。这样的处理十分贴心。
接着,我们可以逐步复制并粘贴这段代码,覆盖原来的内容。灰色部分代表什么呢?灰色部分表示尽管它已经写在代码里,但实际上没有被引用,也就是说下面的代码并没有使用到这部分内容,因此这些using指令是不需要的。我们可以复制这部分内容并粘贴过来,这也是一个很好的操作。
在这种情况下,我们需要继承ILoadingExtension接口。如果出现红色报错,那是因为它告诉我们必须要实现接口的成员。一旦接口定义好了,我们就必须实现这些接口成员,就像之前所说的那样。复制并粘贴这些方法,当我们把所有方法都贴进去后,之前的红色提示错误就不见了。这是因为根据接口的定义,我们必须实现这些方法,而我们已经将这些方法写好了,所以不再报错。
如果你删除其中一些方法,会发现出现错误。例如,如果删掉OnLevelUnloaded,系统会提示你未实现该方法,因为这些方法是必须实现的。如果遇到这种情况,程序将无法编译。当尝试生成解决方案时,会显示生成失败一次,成功零次,因为代码存在错误,无法编译成动态连接库。确保提供接口所需的方法十分重要。
另外,在生成后事件命令行中,也需要将其复制和粘贴到项目属性中。这样可以确保在编译时,能够正确地将生成的动态连接库放置在正确的地方。具体操作是右键点击项目名称,选择属性,找到生成事件,然后在生成后事件命令行处粘贴,并保存。
在编译成功后,我们可以给出基本信息,并且可以进入游戏中查看我们所写的“level is loaded”这条消息是否成功显示。首先,我们要启动游戏并加载我们编译好的模组。在游戏主菜单中,打开内容管理,关闭其他模组,只保留我们要测试的自己编写的模组。然后加载游戏,并选择我们编写的模组,如“CityCT”,然后关闭内容管理,加载一个小城市进行测试。
如果一切顺利,游戏加载后会立即显示“the level is loaded”消息,表示我们的第一阶段目标已经完成。成功学会了在模组加载后显示消息和执行代码。在确认成功后,我们可以继续下一步的教程内容。
第三步是要研究建筑物及其附属道具、广告牌等内容,包括桌子、装饰品、树木花草等。我们的目标是访问并处理这些与建筑物相连的各种元素,比如道具、广告牌等。这些互相关联的元素需要我们来存取和处理。
更具体地说,建筑物的Prefab(预制)是什么意思呢?其实,它类似于我们所说的蓝图。Instance(实例)是根据这个蓝图所创建的具体对象。提到建筑物的Prefabs是建筑物实例的模板。
换句话说,Prefab是建筑物实例化的模板或样板。因为Prefab就是蓝图。这意味着当一个建筑物被创建时,它会使用Prefab中设置好的属性,如纹理、模型以及其他mod中的内容。当我们修改建筑物的蓝图时,也会影响建筑物的实例化,即具体的建筑物也会随之改变。
这个原理是,当我们想要将建筑物上的广告牌、招牌或其他附属物品与建筑物蓝图关联时,一旦修改了蓝图,具体实例也会相应改变。不仅限于建筑物,还包括树木、道具、车辆、交通设置和道路等,它们都有对应的Prefab,即蓝图。
关于Prefab Classes,它代表蓝图的类别,通常在游戏中以“info”结尾,比如BuildingInfo、HarborInfo等。它们实际上都是预先设置好的蓝图,所有建筑物的蓝图可以在这里找到。接下来,我们将学习如何通过循环遍历整个建筑物集合,在场景中找到我们需要的元素,如广告牌、桌子、椅子和装饰品,并通过代码进行操作。
当建筑物消失、移动位置甚至改变颜色时,都有可能。他提到使用以下代码来复写或覆盖"onLabelLoaded"方法。我们将这段代码复制粘贴,然后重写覆盖这个方法,在重新编译后,我们会输出载入的建筑物偏好的总数。
在复写之后,游戏加载后应该会显示地图中加载的建筑物资产数量。接着,我们存档(Ctrl + S),再进行编译(Ctrl + Shift + B)。编译成功后,进入游戏查看是否能够成功显示。
在游戏主界面中选择加载游戏,例如选择"Test City",游戏加载后会显示与建筑物相关的资产数量,比如有1945个。确认后关闭面板,即表示测试成功,之前所述的代码运行正常。
通过循环遍历所有建筑物,使用for循环打印出它们的名称。将这段代码复制并保存,再次编译,然后加载游戏查看结果。你会看到关于建筑物资产的内容,包括不同尺寸的建筑物等,以及列出了所有建筑相关资产的名称,每个代表一个建筑资产的模板或样板。
代码已经编译并且测试完成,我们继续看第四步骤。在第四步中,我们需要使用一个名为"Mod Tools"的工具,来查看每栋建筑物内包含的详细资产列表。如右侧图所示,它列出了建筑物使用的所有资产,包括道具、装饰等。因此,我们必须安装"Mod Tools"这个工具。
可以点击链接进入该工具的页面并订阅。在页面底部,你可以看到该模组支持的版本,如1.13及以上。对于我们的游戏版本而言,应该是兼容的。如果还没有订阅,可以点击订阅按钮,系统会自动将其安装到游戏中。你也可以收藏该模组,以便在重新安装游戏后轻松找到它。
安装好该模组后,我们回到教程中。现在,我们可以加载城市地图,在建筑物上点击,并通过"EXPOWER"浏览器查看详细资产列表。接下来,准备进入游戏。在游戏主菜单中,点击"开始游戏",确保启用刚刚安装的"Mod Tools"模组。然后进入内容管理,选择模组,搜索并打开"Mod Tools",关闭内容管理面板,最后读取游戏,选择有多个建筑物的地图。
进入游戏后,关闭模组界面,选择想要查看的建筑物。选择一个适合观察的角度,使得能够从不同视角查看建筑物,如立体视图,便于观察和研究。
然后,我们左键点击这栋建筑物,就会发现一个弹出面板,这就是"Mod Tools"模组所提供的浏览器,能够查看与该建筑物相关的资产。在右下角有一个控制按钮,点击后可以拖动面板使其变大,以便更好地查看内容。接着,我们可以点击"Building Info",根据教程中的提示,通常带有"info"字样表示为预设资产,类似于蓝图。
在"Building Info"面板中列出了许多关于该建筑物的资产清单。重点查看广告牌的位置。经过一番探索,根据教程指引,我们发现它位于"Unity Engine" -> "Material" -> "Building Info Plus Plus" 这个阵列中,里面包含了该建筑物所使用的道具,如露天阳台的桌椅、花草等装饰以及广告招牌周围的一系列物件,甚至停车场的标线也属于其中。
点击展开加号,显示了相应道具的名称,例如"Plant"可能代表植物,点击"Preview",可以查看其模型网格预览。关注点放在广告招牌上,通常会带有"BDBD"等字符,如"Billboard"。调节可见性为零时,就会隐藏起来,可以轻松定位到广告招牌所在的位置,通常会在建筑物的前方。进行适当调整后,即可找到并查看相关资产信息。
然后,第五个也是一个广告招牌,我们同样可以将其可见性调至零,这样就会消失不见。通过这个例子,我们了解到当调整广告招牌的"PABLIP"的可见度为零时,它就会隐藏起来,因此,将可见性调至零即可让物体不可见。调至70的概率也较大能看见,在10时消失,而100则必定可见。
需要注意的是,我们在浏览器中进行的数值更改并不会写入到游戏存档中,只是暂时性地让我们看到这种变化。如果想在游戏重新加载后移除该广告招牌,我们需通过代码实现。关于如何编写这部分代码,将在下个教程中详细说明。
建议安装并使用Mod Tools提供的场景浏览器,希望大家能在课后体验一下,发现建筑物内隐藏的丰富内容。城市:天际线这款游戏之所以看起来如此逼真,实际上设计相当精妙,一个建筑物内包含如此多元素,可以组合各种道具,呈现出不同的形态,增加了灵活性和创意。
通过这个过程,我有一个感觉,那就是当我们没有使用这个Mod时,我们并不知道建筑物内部竟然隐藏了这么多可以查看的元素。因此,在玩这款游戏时,如果要建立一个庞大的城市,我们的内存一定要足够充足,否则可能会出现显示问题或者程序崩溃等情况,因为实际上这些元素会消耗相当多的内存。
因此,对于这次关于场景浏览器的使用,建议大家亲自体验一下,这样会更加有实感和体会。我们已经学会了如何消除广告招牌,即找到广告招牌的名称,调整其属性中的"PABLIP"将其可见性设置为零即可让其消失。如果设置为100,则会显示出来。通过这种方法来控制广告招牌的显示与隐藏。希望大家能理解这个步骤。
接下来,我们将回到Visual Studio开发工具来编写代码。首先,回顾教程,完成第五步,即使用编程方法来隐藏广告招牌。我们需要将第三部分所编写的面板显示信息删除,并替换为一个循环结构,遍历所有道具资产。需要修改"OnLevelMod"方法,首先复制该方法,然后覆盖修改。
在修改后的方法中,关键字"continue"用于控制循环,当遇到"continue"时,会忽略当前循环下面的内容,继续进行下一个迭代。
当遇到关键字"continue"时,它将忽略下面的代码并继续执行下一个迭代,即查看下一个建筑资产。因此,如果遇到"perfect"这个值等于"nor"或者空值,或者符合预设蓝图中"m_purpose"属性为"mod"或"nor"的情况,都会跳过下面的代码然后进行新的迭代。
相比使用双等号来比较数值,我们可以使用"content"方法来检测所有道具中包含特定名称的道具,而不必等待筛选。我们可以隐藏所有道具中包含"BILIBER"这个词的道具。通过将这些道具的"PABILITY"属性设为零,即可将它们隐藏起来。如果编译和执行成功,含有广告招牌的建筑物将被替换为没有广告招牌的建筑物。我们可以尝试一下。
在重新编译后,我们发现建筑物上的广告招牌确实不见了。点击查看该道具信息时,显示"Building info problem"。这意味着成功隐藏了广告招牌。这样对于其他建筑物也是同样的操作,那些本来有广告招牌的建筑现在都看不到了,广告招牌都已经清除掉了。
这个模组就是用来自动移除广告招牌的。我们已经完成了编写。希望大家觉得这个模组很有趣。通过这个模组开发教程,希望能让大家对《都市天际线》内部运作逻辑有更深入的了解,慢慢学习细微的编程知识,然后按照自己的需求进行改造。本期的教程就到这里结束了。