前言
相关内容来自RimWorld Wiki ,本文仅做翻译和有限的补充,除非另有说明,所属内容均在 CC BY-SA 3.0 下提供。
RimWorld Wiki:https://www.rimworldwiki.com/wiki/Main_PageRimWorld
RimWorld Wiki - XML file structure:https://www.rimworldwiki.com/wiki/Modding_Tutorials/XML_file_structure
水平有限,如有错漏,敬请见谅
如果您喜欢这篇文章,麻烦大家帮我点个赞,谢谢大家的支持!
关于本篇
在本教程中,我们将开始学习XML语法,为什么游戏使用XML,以及XML都做了什么。
你将学习def文件的默认结构:
<?xml version="1.0" encoding="utf-8"?>
<Defs>
<Def Name="Parent" Abstract="True">
</Def>
<Def ParentName="Parent">
</Def>
<!-- 更多<Def> -->
</Defs>
以及如何使用Name
, ParentName
和Abstract
进行继承。
Def的默认结构
标准defs:
可以在Core文件夹中找到Def.xml文件,例如:
Core/
Defs/
ThingDefs_Misc/
Weapons_Guns.xml
`../Mods/Core/Defs/ThingDefs_Misc/Weapons_Guns.xml`
这个文件的第一行是:
<?xml version="1.0" encoding="utf-8"?>
这行告诉我们这个.xml文件使用UTF-8编码并使用1.0版本的XML,这也是这些字段的默认值。部分XML编辑器会在不通知用户的情况下默认隐藏这行,并将其保存在文档顶部。
第一行之后,文件结构由<Defs>
和<Def>
组成:
<Defs>
<Def>
</Def>
<!--其他<Def>标签 -->
<Def>
</Def>
</Defs>
每个<Def>
都包含某个东西的定义(def),用来具体说明每一个可修改的元素和属性。例如:对一个特定的Thing有<ThingDef>
。
<Defs>
的是所有<Def>
的根标签,所有具体类型的<Def>
(如ThingDef, RecipeDef, BiomeDef
等等)都被包含在<Defs>
中。
完整结构:
模组的Def.xml文件需要遵循下例的结构,如果一个.xml文件中存在多个<Defs>
或者有<Def>
处于<Defs>
之外,模组将不能运行。
<?xml version="1.0" encoding="utf-8"?>
<Defs>
<Def>
</Def>
<!--其他<Def>标签 -->
<Def>
</Def>
</Defs>
语法和关键词
你肯定?已经注意到,XML文件中的一些单词似乎被反复使用。注意单词Abstract, Name和Class。这些词被称为关键词,它们是了XML语言语法的一部分。一个词被当做关键词使用意味着它有一个预期目的,这个词也只能作为关键词使用。出于这个原因,你应该了解一些规则,因为违反这些规则是导致bug和错误的常见原因。
当你使用关键词的时候,每次都应该完全一致的拼写。一个常见的错误是无意中使用了不同的大小写。例如,对于关键词Abstract来说,Abstract(√)和abstract(×)是不同的,单词拼写错误会引起一些问题。
一般来说,在编辑或创建新的<Def>
时应避免使用关键词作为属性。几乎可以肯定的是,这样做会导致问题,所以最好避免使用超出其预期用途的关键词。
继承
摘要: 在XML中,继承用于减少冗余。如果某个标签有ParentName="YourParentName"
属性,它会获取具有Name="YourParentName"
属性的标签的所有内容。如果这个父类标签不完整,将在加载时导致游戏崩溃,可以用Abstract="True"
来阻止它被加载到游戏中。
注意:在A15更新之后,抽象类型(Abstract)的标签已经可以直接从Core和其他模组继承。重新定义Core的抽象标签是导致模组兼容性问题的常见原因,因为在抽象标签被重新定义后加载的任何模组都将使用修改后的版本而不是Core的原始版本。因此,在为模组定义抽象标签时,建议尽量避免覆盖Core的抽象标签,而是使用不太可能被其他模组意外使用的唯一名称。
Basegun ThingDef
文件ThingDefs_Misc\Weapons\BaseWeapons.xml
中首先是一个带有Name和Abstract 属性的<ThingDef>
标签:
<ThingDef Abstract="True" Name="BaseWeapon">
Abstract="True"
意味着这个<ThingDef>
不会加载到游戏中,而是在读取和处理这个<ThingDef>
中的内容后,继续读取XML文件,在留下任何可能复制(获取,继承)于该<ThingDef>
的内容后丢弃这个<ThingDef>
。这一系列操作都是通过这样一个标签完成的:
<ThingDef Abstract="True">
这意味着<ThingDef Abstract="True" Name="BaseWeapon">
的所有内容可以在游戏中反复使用,但BaseWeapon本身并不存在于游戏中 —— 这就是抽象。
Name属性表示这个<ThingDef>
的内容可以被另一个<ThingDef>
继承(读取 | 复制)。因此,你可以将所有在整个文件中重复的内容写在一个位置,例如:
<category>Item</category>
上例表示该Thing属于物品(Item),而不是建筑(Building)或其他什么东西。因为在各种内容中都有大量重复的标签,这么做可以极大地压缩XML文件。
完整的BaseWeapon只需要在文件中定义一次,然后可以通过以下方式继承(复制):
<ThingDef ParentName="BaseWeapon">
<ThingDef ParentName="Parent">
将继承<ThingDef Name="Parent">
的所有内容。
另一个常见父类标签是BaseBullet,它包含各种普通子弹Def中的常见的重复元素,比如子弹不使用生命值(hitpoints):
<useHitPoints>False</useHitPoints>
文件ThingDefs_Misc\Weapons\BaseWeapons.xml
中的下一个Def是:
<ThingDef Name="BaseGun" Abstract="True" ParentName="BaseWeapon">
它继承了BaseWeapon的内容,并被所有带有ParentName="BaseGun"
的ThingDefs继承。
把每个<ThingDef Name ="...">
看作一种模板可能会有所帮助:
- BaseWeapon包含了适用于所有武器的基本信息:它们可以被拖拽、装备、选择;
- BaseGun包含了所有枪械的基本信息:它们是WeaponsRanged类的一部分,可以被冶炼,可以有艺术;
- BaseMakeableGun依次包含可制作的枪械的信息。
下表的内容是继承在Rimworld的XML中的工作方式,它可能会对你有所帮助,记得在阅读下面的内容后整理一下思绪:
- 游戏启动;
- 游戏开始单独的加载每个模组;
- 从每个标签中取得继承信息;
- 任何具有ParentName属性的标签都将继承(读取 | 复制)并应用具有与其关联的Name属性的标签的所有内容。
- 子类标签得到内容;
- 父类标签提供内容;
- 一个标签可能同时是子类标签和父类标签;
- Path操作在继承发生之前就完成了,也就是说此时子类继承的是被Path过的父类。
- 内容信息(读取:在
<ThingDef>
之间的所有内容)被获取并应用于每个标签;- 来自父类标签的内容此时被覆盖;
- 所有的
<Def>
都获取了完整的内容之后,抽象(Abstract)类的 def 就会被丢弃,从而被游戏忽略; - 所有的
<Def>
及其内容完成加载。
- 模组加载完成。
图解(译者附)
这段翻译的太诡异了,想来想去还是附个图解把。
假设我们在XML中定义了三个<ThingDef>
:
BaseWeapon:
Thing A:
Thing B:
加载过程:
- 执行Path操作。如果
BaseWeapon
是Path的目标,那么BaseWeapon
将在此时被修改,之后使用的BaseWeapon
都是被修改(Path)过的; - 加载
Thing A
,在加载Thing A
时在会发现ParentName="BaseWeapon"
; - 首先继承
BaseWeapon
的内容,此时Thing A
是这样的:
- 在继承完
BaseWeapon
的内容后,再加载Thing A
自己的内容:
- 如果
Thing A
自己的内容与BaseWeapon
的内容有重叠,以Thing A
自己的内容优先,来自BaseWeapon
的内容会被覆盖; - 加载
Thing B
的时候也一样:
- 继续加载,直到所有继承了
BaseWeapon
的Thing全都加载完; - 再之后
BaseWeapon
就没用了,因为它有Abstract="True"
属性,游戏会直接丢弃它,不会真正加载:
代码解释(有修改)
你在XML文件中编写的内容:
<ThingDef Name="BaseGun" Abstract="True">
<exampleTagOne>true</exampleTagOne>
</ThingDef>
<ThingDef Name="BaseHumanGun" ParentName="BaseGun" Abstract="True">
<OtherTag>true</OtherTag>
<someOtherTag>12</someOtherTag>
</ThingDef>
<ThingDef ParentName="BaseHumanGun">
<defName>Test_Gun</defName>
<someOtherTag>24</someOtherTag>
</ThingDef>
游戏最终得到的内容:
<ThingDef>
<defName>Test_Gun</defName> <!--自身内容-->
<exampleTagOne>true</exampleTagOne> <!--继承自BaseGun的内容-->
<OtherTag>true</OtherTag> <!--继承自BaseHumanGun的内容-->
<someOtherTag>24</someOtherTag> <!--自身内容-->
<!-- <someOtherTag>在BaseHumanGun和自身中都存在,以自身优先 -->
</ThingDef>
<ThingDef Name="BaseGun" Abstract="True"> | |
---|---|
<ThingDef> | 该标签的名称,由游戏读取并根据该名称处理成正确的定义。 所有位于../Mods/Core/Defs/ThingDefs/都使用<ThingDef> 标签。 |
Name="BaseGun" | 该标签的Name属性。表示标签可以用作父标签,它的Name属性值是“BaseGun”. |
Abstract="True" | 该标签的Abstract属性为true,表示该标签是一个抽象类型标签。 这意味着该标签的内容不会实例化,它的内容只能被其他标签继承,而不会被加载到游戏中。该标签的唯一目的就是作为父标签被继承。 <ThingDef> 的唯一目的是作为父标签被继承吗? 是:使用Abstract="True"; 否:不要使用该属性。 |
<ThingDef Name="BaseHumanGun" ParentName="BaseGun" Abstract="True"> | |
ParentName="BaseGun" | 该标签的ParentName属性。表示该标签将继承Name值为“BaseGun”的父标签。 |
完整代码
添加继承之后,我们的XML文件结构看起来像这样:
<?xml version="1.0" encoding="utf-8"?>
<Defs>
<Def Name="Parent" Abstract="True">
</Def>
<Def ParentName="Parent">
</Def>
<!-- 更多<Def> -->
</Defs>
<Defs>
是根节点,这是强制要求;<Def>
必须有一个特定的名称,与C#中的类型相匹配,如<ThingDef>
。
停止继承
在某些情况下,你不希望子类标签使用父类标签中一些的值。你可以通过在子类标签中指定inherit ="False"
来告诉子标签不要继承父类标签的部分内容,以狙击步枪为例:
<weaponTags Inherit="False">
<li>SniperRifle</li>
</weaponTags>
上例将只使用SniperRifle作为<weaponTags>
,而不继承父类<weaponTags>
标签。这对列表元素特别有用,因为列表元素的继承方式是添加。
这样做的好处是,可以在保持父类标签不变的情况下让子类选择要继承的元素和属性。
普通元素与列表元素继承的不同(译者附)
上面提到了当子类标签具有和父类标签重叠的内容时,以子类标签自己的内容优先,来自父类标签的内容会被覆盖。
如:
<!--父类标签-->
<ThingDef Name="ParentTag" Abstract="True">
<CommonTag>Value_A</CommonTag>
</ThingDef>
<!--子类标签-->
<ThingDef ParentName="ParentTag">
<def>TagDef</def>
<CommonTag>Value_B</CommonTag>
<OtherTag>Other_Value</OtherTag>
</ThingDef>
<!--游戏得到的最终标签-->
<ThingDef>
<def>TagDef</def>
<CommonTag>Value_B</CommonTag> <!--子类内容保留,父类内容被覆盖-->
<OtherTag>Other_Value</OtherTag>
</ThingDef>
因此,如果要改写父类标签中的普通元素,只需在子类中重新赋值该元素即可。但如果是列表元素,情况会有所不同。子类标签不会覆盖掉父类标签的列表内容,仅仅会将自己的内容添加到列表中。
如:
<!--父类标签-->
<ThingDef Name="ParentTag" Abstract="True">
<ListTag>
<li>Value_A<li>
</ListTag>
</ThingDef>
<!--子类标签-->
<ThingDef ParentName="ParentTag">
<def>TagDef</def>
<ListTag>
<li>Value_B<li>
</ListTag>
<OtherTag>Other_Value</OtherTag>
</ThingDef>
<!--游戏得到的最终标签-->
<ThingDef>
<def>TagDef</def>
<ListTag>
<li>Value_A<li> <!--父类内容保留-->
<li>Value_B<li> <!--子类内容保留-->
</ListTag>
<OtherTag>Other_Value</OtherTag>
</ThingDef>
因为,如果要改写父类标签中已经赋值的列表元素,必须停止该列表元素的继承(使用inherit ="False"
属性),并在子类中完全重新赋值。
如:
<!--父类标签-->
<ThingDef Name="ParentTag" Abstract="True">
<ListTag>
<li>Value_A1<li>
<li>Value_A2<li>
</ListTag>
</ThingDef>
<!-- 假定我们只想使用Value_A1,而不想使用Value_A2 -->
<!--子类标签-->
<ThingDef ParentName="ParentTag">
<def>TagDef</def>
<!--必须为ListTag重新赋值Value_A1,因为来自父类继承被停止了-->
<ListTag inherit ="False">
<li>Value_A1<li>
<li>Value_B<li>
</ListTag>
<OtherTag>Other_Value</OtherTag>
</ThingDef>
<!--游戏得到的最终标签-->
<ThingDef>
<def>TagDef</def>
<ListTag>
<li>Value_A1<li>
<li>Value_B<li>
</ListTag>
<OtherTag>Other_Value</OtherTag>
</ThingDef>
备注:在<li>
标签上添加inherit ="False"
属性没有意义。因为每一个<li>
本质上都是不同的项,即使为<li>
添加inherit ="False"
,它也不知道自己要停止继承什么。
示例:
<ThingDef Name="ThingOne" Abstract="True">
<comps>
<li Class = "CompOne">
<valueA>1</valueA>
</li>
</comps>
</ThingDef>
<ThingDef ParentName="ThingOne">
<comps Inherit= "False"> <!-- 表示不从父标签中继承comps -->
<li Class = "CompOne"> <!-- <li Class = "CompOne">虽然存在于父标签的comps中,但我们并没有继承父标签的comps,因此必须重新编写 -->
<valueA>2</valueA> <!-- 同时可以添加一些新的内容 -->
</li>
</comps>
</ThingDef>