Rimworld Mod制作教程1 认识Mod结构
0.简介之前就有想尝试过mod制作,结果在网上看了N多教程还是不知道从何下手。后来想到解铃还须系铃人,无奈去看英文的文档,觉得会详细一些。所以本篇参考了 wiki上的一篇教程但是即便是非常详细的教程,对于第一次尝试的人来说还是有很多容易踩坑的地方,所以本篇记录完整的武器制作流程以及需要注意的地方,还有一些翻译工作。1.mod简介首先我介绍一下,rimworld的mod主要有四个部分,分别...
文章目录
废话
之前就有想尝试过mod制作,结果在网上看了N多教程还是不知道从何下手。后来想到解铃还须系铃人,无奈去看英文的文档,觉得会详细一些。
所以本篇参考了 wiki上的一篇教程。但是即便是非常详细的教程,对于第一次尝试的人来说还是有很多容易踩坑的地方,所以本篇记录踩坑过程。
核心内容
1 认识mod结构
1.1 mod结构
- Mod根目录
- Abount(存放mod的简介和mod的标题图片)
- Defs(数据定义)
- Assemblies(mod代码打包后的库文件)
- Languages(翻译的核心目录)
- Textures(纹理,mod中对象的素材)
- Sounds(声音)
- Patches(针对其他mod的补丁)
- Source(mod的源码,一般没有作者好心给出源码,不过我们可以反编译就是了,很少有加密的mod)
2 认识开发工具
2.1 开发工具
visual studio:用于编写mod核心代码
vscode:用于编辑xml数据定义
dsnpy:用于反编译原游戏或其他mod的源代码来学习新姿势
下载地址请直接百度, 要学会善用搜索引擎
本篇使用以上落后ide开发,目前有更好用的开发工具jetbrain rider, 体积更轻便,运行更快,并且可以完成上面3个工具的功能,你有什么理由拒绝最新的工具呢?后面会找时间写一篇教程介绍如何使用rider开发mod
2.2 visual studio开发环境
百度下载,安装时勾选.net桌面开发,否则无法创建C#程序集,也就是无法打包dll出来。剩下的一路下一步。
2.3 程序集构建
我们在vs上新建一个类库(.net framework),并把名字改成命名空间SR
wiki的教程提醒我们使用.net framework 3.5的框架版本,我尝试之后发现原游戏的版本已经高于3.5,于是我反编译其他mod看了一下其他作者使用的版本,选择了相同4.6.1
2.4 修改编译输出路径
在properties中点生成,把dll输出路径改到我们的mod路径E:\steam\steamapps\common\RimWorld\Mods\TestGun\Assemblies里
接下来点击高级,设置内部编译器错误报告为无,这样做是为了避免我们写出一些小bug导致整个游戏崩溃。
2.5 添加引用库
点击引用,选择原游戏下RimWorld\RimWorldWin64_Data\Managed路径下UnityEngine,UnityEngine.CoreModule和Assembly-CSharp.dll三个库文件。
右键选择引用文件,在属性中取消“复制到本地”,否则编译时会多复制一堆引用库文件,而这些文件已经被原游戏添加过一次了。
下图有部分内容是错的,实际不需要引用这么多库文件,只需要上面提到的3个,以及自带的部分。
2.6 编写代码及编译
代码编写过程略。
右键方案点击build即可打包编译。
3 案例
此部分暂时只做了解,能看懂多少算多少。
3.1 mod结构构建
为了区分哪些变量是游戏中的关键词,我更换了一下命名,我们的mod叫做TestGun(测试枪).
按照上面的教程,构建一套mod文件结构。并丢到rimworld的mod文件夹下,在这个文件夹下的mod会被游戏识别,并且可以随时发布。
3.2 修改简介
然后进入About目录,拖进去一张图片改名叫Preview.png,再创建一个txt文件,然后改成About.xml,如果你看不到.xml的后缀,在上方点击查看–选项,取消隐藏已知文件类型的扩展名
然后打开About.xml
复制以下代码,分别是这个mod的名字,作者,版本和描述
<?xml version="1.0" encoding="utf-8"?>
<ModMetaData>
<name>TestGun</name>
<author>Shadowrabbit</author>
<supportedVersions>
<li>1.1</li>
</supportedVersions>
<description>测试创造mod物品
</description>
</ModMetaData>
完成了这一步,就可以在游戏中看到我们的mod
这里非常推荐一种mod命名规则:
[{作者名缩写}{支持的版本号}]{mod名称}
例如:[SR1.2]Test Gun
这种规范可以让人一眼了解这个mod是否适用于当前版本
3.3 创建数据定义
接下来在Defs那个文件夹内创建一个ThingDefs的文件夹,然后在ThingDefs内创建一个xml文件叫做RangedWeapon_TestGun
这是原游戏的命名规则,不遵守也可以正常运行,但我还是推荐认真一点命名。
接下来在xml中更改一下格式。
<?xml version="1.0" encoding="utf-8"?>
<Defs>
</Defs>
然后我们要去“抄”一下原游戏中某个枪的xml数据,这样我们才能知道默认都有哪些数据能修改。
我们在vscode中添加一个目录E:\steam\steamapps\common\RimWorld\Data\Core,这是我的游戏目录仅供参考,要填自己电脑安装的目录
core是原游戏的核心,可以发现它的结构和我们mod的定义是完全一样的。打开Defs可以找到所有的物品数据定义,但是太多了反而不知道从哪看起。我们在core中搜索Revolver(手枪),把手枪的数据复制过来
为了方便观察,我加了一些翻译注释
<?xml version="1.0" encoding="utf-8"?>
<!-- 子弹的数据 -->
<Defs>
<!-- 继承了哪个物品 -->
<ThingDef ParentName="BaseBullet">
<!-- 代码中物品的名字 -->
<defName>Bullet_Revolver</defName>
<!-- 游戏中物品显示的名字 -->
<label>revolver bullet</label>
<graphicData>
<!-- 使用的纹理贴图 -->
<texPath>Things/Projectile/Bullet_Small</texPath>
<!-- 使用哪个图形类的代码 -->
<graphicClass>Graphic_Single</graphicClass>
</graphicData>
<projectile>
<!-- 子弹类型 -->
<damageDef>Bullet</damageDef>
<!-- 子弹基础伤害 -->
<damageAmountBase>12</damageAmountBase>
<!-- 取消射击的反应速度 -->
<stoppingPower>1</stoppingPower>
<!-- 子弹的速度 -->
<speed>55</speed>
</projectile>
</ThingDef>
<!-- 枪的数据 -->
<ThingDef ParentName="BaseHumanMakeableGun">
<defName>Gun_Revolver</defName>
<label>revolver</label>
<!-- 描述 -->
<description>An ancient pattern double-action revolver. It's not very powerful, but has a decent range for a pistol and is quick on the draw.</description>
<graphicData>
<texPath>Things/Item/Equipment/WeaponRanged/Revolver</texPath>
<graphicClass>Graphic_Single</graphicClass>
</graphicData>
<!-- UI缩放 -->
<uiIconScale>1.4</uiIconScale>
<soundInteract>Interact_Revolver</soundInteract>
<!-- 个人猜测是艺术系统的标签 -->
<thingSetMakerTags><li>RewardStandardQualitySuper</li></thingSetMakerTags>
<!-- 基础属性 -->
<statBases>
<!-- 制作的工作量 -->
<WorkToMake>4000</WorkToMake>
<!-- 物品大小 -->
<Mass>1.4</Mass>
<!-- 贴脸的命中率 -->
<AccuracyTouch>0.80</AccuracyTouch>
<!-- 短距离命中率 -->
<AccuracyShort>0.75</AccuracyShort>
<!-- 中距离命中率 -->
<AccuracyMedium>0.45</AccuracyMedium>
<!-- 远距离命中率 -->
<AccuracyLong>0.35</AccuracyLong>
<RangedWeapon_Cooldown>1.6</RangedWeapon_Cooldown>
</statBases>
<!-- 武器标签 -->
<weaponTags>
<li>SimpleGun</li>
<li>Revolver</li>
</weaponTags>
<!-- 消耗列表 -->
<costList>
<Steel>30</Steel>
<ComponentIndustrial>2</ComponentIndustrial>
</costList>
<recipeMaker>
<!-- 技能需求 -->
<skillRequirements>
<Crafting>3</Crafting>
</skillRequirements>
</recipeMaker>
<!-- 动作 -->
<verbs>
<li>
<!-- 射击 -->
<verbClass>Verb_Shoot</verbClass>
<!-- 是否有标准命令 -->
<hasStandardCommand>true</hasStandardCommand>
<!-- 子弹类型 -->
<defaultProjectile>Bullet_Revolver</defaultProjectile>
<!-- 预热时间 -->
<warmupTime>0.3</warmupTime>
<!-- 射击距离 -->
<range>25.9</range>
<!-- 射击时使用的音效 -->
<soundCast>Shot_Revolver</soundCast>
<!-- 中弹时的音效 -->
<soundCastTail>GunTail_Light</soundCastTail>
<!-- 枪口光效的缩放 -->
<muzzleFlashScale>9</muzzleFlashScale>
</li>
</verbs>
<tools>
<li>
<label>grip</label>
<capacities>
<li>Blunt</li>
</capacities>
<power>9</power>
<cooldownTime>2</cooldownTime>
</li>
<li>
<label>barrel</label>
<capacities>
<li>Blunt</li>
<li>Poke</li>
</capacities>
<power>9</power>
<cooldownTime>2</cooldownTime>
</li>
</tools>
</ThingDef>
</Defs>
到这里为止就可以使用xml修改枪的各种参数,以及发射什么样的子弹(需要去core里再查查都有什么子弹,然后填在defaultProjectile里),还可以更换枪的贴图和音效。更多的物品参数需要查查wiki,然后翻译+自己理解,还有多看看core中的xml代码。
我们本次要制作的是带有瘟疫效果的枪,我们需要创建一种新的子弹,所以我们要在子弹的def里加几个新参数与C#代码交互
在子弹的xml数据中添加
<ThingDef Class="SR.ThingDef_TestBullet" ParentName="BaseBullet">
和三个自定义的新属性
<!-- 触发瘟疫的概率 -->
<AddHediffChance>0.5</AddHediffChance>
<!-- Hediff这个词是游戏里独一无二的名字,字典里查不到,他的含义是某个部位上的状态,比如感染,仿生部位,瘟疫Plague是原版游戏其中的一种 -->
<HediffToAdd>Plague</HediffToAdd>
<!-- 该物品在C#中使用的逻辑类 -->
<thingClass>SR.Projectile_PlagueBullet</thingClass>
然后修改子弹的命名避免和原版一样,修改后如下
<?xml version="1.0" encoding="utf-8"?>
<!-- 子弹的数据 -->
<Defs>
<!--该物品在C#中使用的数据类 继承了哪个物品 -->
<ThingDef Class="SR.ThingDef_TestBullet" ParentName="BaseBullet">
<!-- 代码中物品的名字 -->
<defName>Bullet_TestBullet</defName>
<!-- 游戏中物品显示的名字 -->
<label>测试子弹</label>
<graphicData>
<!-- 使用的纹理贴图 -->
<texPath>Things/Projectile/Bullet_Small</texPath>
<!-- 使用哪个图形类的代码 -->
<graphicClass>Graphic_Single</graphicClass>
</graphicData>
<projectile>
<!-- 子弹类型 -->
<damageDef>Bullet</damageDef>
<!-- 子弹基础伤害 -->
<damageAmountBase>12</damageAmountBase>
<!-- 取消射击的反应速度 -->
<stoppingPower>1</stoppingPower>
<!-- 子弹的速度 -->
<speed>55</speed>
</projectile>
<!-- 触发瘟疫的概率 -->
<addHediffChance>0.5</addHediffChance>
<!-- Hediff这个词是游戏里独一无二的名字,字典里查不到,他的含义是某个部位上的状态,比如感染,仿生部位,瘟疫Plague是原版游戏其中的一种,我们定义的这个变量意义是储存附加给中弹生物的hediff类型 -->
<hediffToAdd>Plague</hediffToAdd>
<!-- 该物品在C#中使用的逻辑类 -->
<thingClass>SR.Projectile_TestBullet</thingClass>
</ThingDef>
<!-- 枪的数据 -->
<ThingDef ParentName="BaseHumanMakeableGun">
<defName>SR_Gun_TestGun</defName>
<label>影兔的测试枪</label>
<!-- 描述 -->
<description>影兔测试瘟疫效果的枪</description>
<graphicData>
<texPath>Things/Item/Equipment/WeaponRanged/Revolver</texPath>
<graphicClass>Graphic_Single</graphicClass>
</graphicData>
<!-- UI缩放 -->
<uiIconScale>1.4</uiIconScale>
<!-- 交互的音效 -->
<soundInteract>Interact_Revolver</soundInteract>
<!-- 个人猜测是艺术系统的标签 -->
<thingSetMakerTags><li>RewardStandardQualitySuper</li></thingSetMakerTags>
<!-- 基础属性 -->
<statBases>
<!-- 制作的工作量 -->
<WorkToMake>4000</WorkToMake>
<!-- 物品大小 -->
<Mass>1.4</Mass>
<!-- 贴脸的命中率 -->
<AccuracyTouch>0.80</AccuracyTouch>
<!-- 短距离命中率 -->
<AccuracyShort>0.75</AccuracyShort>
<!-- 中距离命中率 -->
<AccuracyMedium>0.45</AccuracyMedium>
<!-- 远距离命中率 -->
<AccuracyLong>0.35</AccuracyLong>
<RangedWeapon_Cooldown>1.6</RangedWeapon_Cooldown>
</statBases>
<!-- 武器标签 -->
<weaponTags>
<li>SimpleGun</li>
<li>Revolver</li>
</weaponTags>
<!-- 消耗列表 -->
<costList>
<Steel>30</Steel>
<ComponentIndustrial>2</ComponentIndustrial>
</costList>
<recipeMaker>
<!-- 技能需求 -->
<skillRequirements>
<Crafting>3</Crafting>
</skillRequirements>
</recipeMaker>
<!-- 动作 -->
<verbs>
<li>
<!-- 射击 -->
<verbClass>Verb_Shoot</verbClass>
<!-- 是否有标准命令 -->
<hasStandardCommand>true</hasStandardCommand>
<!-- 子弹类型 -->
<defaultProjectile>Bullet_TestBullet</defaultProjectile>
<!-- 预热时间 -->
<warmupTime>0.3</warmupTime>
<!-- 射击距离 -->
<range>25.9</range>
<!-- 射击时使用的音效 -->
<soundCast>Shot_Revolver</soundCast>
<!-- 中弹时的音效 -->
<soundCastTail>GunTail_Light</soundCastTail>
<!-- 枪口光效的缩放 -->
<muzzleFlashScale>9</muzzleFlashScale>
</li>
</verbs>
<tools>
<li>
<label>grip</label>
<capacities>
<li>Blunt</li>
</capacities>
<power>9</power>
<cooldownTime>2</cooldownTime>
</li>
<li>
<label>barrel</label>
<capacities>
<li>Blunt</li>
<li>Poke</li>
</capacities>
<power>9</power>
<cooldownTime>2</cooldownTime>
</li>
</tools>
</ThingDef>
</Defs>
为了避免与其他mod命名冲突,我使用SR作为代码的命名空间,SR.XX表示C#类,SR_XX表示xml数据,这也是原游戏默认的命名规范,有两处SR.XX的数据是与C#进行交互的,命名必须一致。
至此xml部分就结束了。
3.4 编写C#代码
using RimWorld;
using Verse;
namespace SR
{
public class Projectile_TestBullet:Bullet
{
}
}
using RimWorld;
using Verse;
namespace SR
{
public class ThingDef_TestBullet:ThingDef
{
}
}
我们把新添加的字段写入数据类中,名字要与xml中的一致,不需要给新变量赋默认值,因为会被xml中的数据覆盖,所以addHediffChance运行时为我们在xml中设置的0.5
using RimWorld;
using Verse;
namespace SR
{
public class ThingDef_TestBullet:ThingDef
{
public float addHediffChance; //默认值会被xml覆盖
public HediffDef hediffToAdd;
}
}
题外话,说一个wiki上关于老版本的问题,如果HediffDef类型的数据有默认值的话,在1.0版本之后是会报错的,因为这个时候HediffDefOf还没有初始化。不过假如你就是想留默认值也有解决方法,有一个回调函数,我们在回调的时候重新赋值即可。
public override void ResolveReferences()
{
base.ResolveReferences();
hediffToAdd = HediffDefOf.Plague;
}
之后是逻辑类,先定义一个变量存它的数据
#region data
public ThingDef_TestBullet ThingDef_TestBullet
{
get
{
//底层通过名字读取了我们定义的ThingDef_TestBullet这个xml格式的新数据,并存放到了this.def中,我们将this.def拆箱拿到我们定义好的ThingDef_TestBullet格式数据
return this.def as ThingDef_TestBullet
}
}
#endregion
我们需要知道原版子弹击中目标会执行什么流程,打开dnspy反编译Assembly-CSharp.dll,原游戏所有代码都在Assembly-CSharp.dll中,然后搜索bullet子弹,可以看到子弹的源码里只有一个可以重载的方法impact,根据英文的意思知道他是中弹后执行的逻辑,里面写了什么我们暂时不需要关心,我们在测试子弹的类中继承子弹这个类,然后重写impact方法,在原逻辑执行完毕后添加我们关于瘟疫的设定
关于添加瘟疫的设定,wiki上给出了代码,我详细解读一下贴出来
using RimWorld;
using Verse;
namespace SR
{
public class Projectile_TestBullet : Bullet
{
#region data
public ThingDef_TestBullet ThingDef_TestBullet
{
get
{
//底层通过名字读取了我们定义的ThingDef_TestBullet这个xml格式的新数据,并存放到了this.def中,我们将this.def拆箱拿到我们定义好的ThingDef_TestBullet格式数据
return this.def as ThingDef_TestBullet;
}
}
#endregion
protected override void Impact(Thing hitThing)
{
//子弹的影响,底层实现了伤害 击杀之类的方法,感兴趣的话可以用dnspy反编译Assembly-Csharp.dll研究里面到底写了什么
base.Impact(hitThing);
//绝大多数mod报错都是因为没判断好非空,写注释和判断非空是好习惯
//大佬在这里用了一个语法糖hitThing is Pawn hitPawn
//如果hitThing可以被拆箱为Pawn的话 这个值返回true并且会声明一个变量hitPawn=hitThing as Pawn
//否则返回false hitPawn是null
if (ThingDef_TestBullet != null && hitThing != null && hitThing is Pawn hitPawn)
{
var rand = Rand.Value; //这个方法封装了一个返回0%-100%随机数的函数
//触发瘟疫
if (rand <= ThingDef_TestBullet.addHediffChance)
{
//在屏幕左上角显示提示,translate方法用于翻译不同语言之后再说,MessageTypeDefOf要设置一种事件
Messages.Message("{0}使用测试枪导致{1}感染瘟疫".Translate(this.launcher.Label,hitPawn.Label),MessageTypeDefOf.NeutralEvent);
//判断一下目标是否已经触发了瘟疫效果
var plagueOnPawn = hitPawn.health?.hediffSet?.GetFirstHediffOfDef(ThingDef_TestBullet.hediffToAdd);
//我们为本次触发的瘟疫随机生成一个严重程度
var randomSeverity = Rand.Range(0.15f, 0.30f);
//已经触发瘟疫
if (plagueOnPawn != null)
{
//严重程度叠加,超过100%会即死
plagueOnPawn.Severity += randomSeverity;
}
else
{
//我们调用HediffMaker.MakeHediff生成一个新的hediff状态,类型就是我们之前设置过的HediffDefOf.Plague瘟疫类型
Hediff hediff = HediffMaker.MakeHediff(ThingDef_TestBullet.hediffToAdd, hitPawn);
//设置这个状态的严重程度
hediff.Severity = randomSeverity;
//把状态添加到被击中的目标身上
hitPawn.health.AddHediff(hediff);
}
}
//本次没有触发
else
{
//这个方法可以在某个位置(这里是被击中目标的身旁)弹出一小行字,比如未击中,击中头部之类的,也是可以
MoteMaker.ThrowText(hitThing.PositionHeld.ToVector3(), hitThing.MapHeld, "{0}未触发瘟疫".Translate(hitPawn.Label), 12f);
}
}
}
}
}
至此新武器的mod就制作完毕了,我们选择打包生成新的dll,根据之前设置的目录,会打包在Assemblies中。
3.5 测试
很遗憾我们的新武器虽然已经有了数据,但是没有任何方法可以在游戏中自然生成,我们需要借助开发者模式来测试我们的新枪支,首先打开开发者模式(记得加载我们的mod),随便建个新地图,然后在上方选择open debug actions menu
在里面找到spawn weapon – SR_Gun_TestGun,然后把它丢在地上,让我们的角色捡起来,然后对着自己的小人开一枪试试,刚好触发了瘟疫效果
未触发的log也正常显示
叠加到100%瘟疫会直接死亡,也是正常的。至此新武器的制作就完成了。
3.6 本地化
本地化就是指翻译成各种语言,我们把之前message中的内容用一个变量代替,让这个变量在各种语言下显示不同的文字就可以了。
Messages.Message("{0}使用测试枪导致{1}感染瘟疫".Translate(this.launcher.Label,hitPawn.Label),MessageTypeDefOf.NeutralEvent);
我们更改为
Messages.Message("SR_Message_TestBullet_Success".Translate(this.launcher.Label,hitPawn.Label),MessageTypeDefOf.NeutralEvent);
之后在Languages中创建ChineseSimplified目录,然后在ChineseSimplified目录下创建Keyed目录,再在Keyed目录下创建一个xml文件,我们命名为SR_TestGun_Keys.xml(名称可以随便取,但还是遵循规范).复制默认的语言数据结构
<?xml version="1.0" encoding="utf-8" ?>
<LanguageData>
</LanguageData>
之后添加两个新的key,对应我们上一步设置的key名字
<?xml version="1.0" encoding="utf-8" ?>
<LanguageData>
<SR_Message_TestBullet_Success></SR_Message_TestBullet_Success>
<SR_Mote_TestBullet_Fail></SR_Mote_TestBullet_Fail>
</LanguageData>
再把中文的翻译写进去
<?xml version="1.0" encoding="utf-8" ?>
<LanguageData>
<SR_Message_TestBullet_Success>{0}使用测试枪导致{1}感染瘟疫</SR_Message_TestBullet_Success>
<SR_Mote_TestBullet_Fail>{0}未触发瘟疫</SR_Mote_TestBullet_Fail>
</LanguageData>
至此中文版本就完成了,如果要加入英文版的话,就在Luaguages下创建English目录等等,然后创建相同的文件,在值里填入英文对应的文本即可,游戏会根据用户选择的语言自动选择语言数据加载。
源码下载
如果这篇文章对你有帮助,点赞收藏支持一下呗!
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)