之前就有想尝试过mod制作,结果在网上看了N多教程还是不知道从何下手。后来想到解铃还须系铃人,无奈去看英文的文档,觉得会详细一些。
所以本篇参考了 wiki上的一篇教程。但是即便是非常详细的教程,对于第一次尝试的人来说还是有很多容易踩坑的地方,所以本篇记录踩坑过程。
visual studio:用于编写mod核心代码
vscode:用于编辑xml数据定义
dsnpy:用于反编译原游戏或其他mod的源代码来学习新姿势
下载地址请直接百度, 要学会善用搜索引擎
本篇使用以上落后ide开发,目前有更好用的开发工具jetbrain rider, 体积更轻便,运行更快,并且可以完成上面3个工具的功能,你有什么理由拒绝最新的工具呢?后面会找时间写一篇教程介绍如何使用rider开发mod
百度下载,安装时勾选.net桌面开发,否则无法创建C#程序集,也就是无法打包dll出来。剩下的一路下一步。
我们在vs上新建一个类库(.net framework),并把名字改成命名空间SR
wiki的教程提醒我们使用.net framework 3.5的框架版本,我尝试之后发现原游戏的版本已经高于3.5,于是我反编译其他mod看了一下其他作者使用的版本,选择了相同4.6.1
在properties中点生成,把dll输出路径改到我们的mod路径E:\steam\steamapps\common\RimWorld\Mods\TestGun\Assemblies里
接下来点击高级,设置内部编译器错误报告为无,这样做是为了避免我们写出一些小bug导致整个游戏崩溃。
点击引用,选择原游戏下RimWorld\RimWorldWin64_Data\Managed路径下UnityEngine,UnityEngine.CoreModule和Assembly-CSharp.dll三个库文件。
右键选择引用文件,在属性中取消“复制到本地”,否则编译时会多复制一堆引用库文件,而这些文件已经被原游戏添加过一次了。
下图有部分内容是错的,实际不需要引用这么多库文件,只需要上面提到的3个,以及自带的部分。
代码编写过程略。
右键方案点击build即可打包编译。
此部分暂时只做了解,能看懂多少算多少。
为了区分哪些变量是游戏中的关键词,我更换了一下命名,我们的mod叫做TestGun(测试枪).
按照上面的教程,构建一套mod文件结构。并丢到rimworld的mod文件夹下,在这个文件夹下的mod会被游戏识别,并且可以随时发布。
然后进入About目录,拖进去一张图片改名叫Preview.png,再创建一个txt文件,然后改成About.xml,如果你看不到.xml的后缀,在上方点击查看–选项,取消隐藏已知文件类型的扩展名
然后打开About.xml
复制以下代码,分别是这个mod的名字,作者,版本和描述
TestGun
Shadowrabbit
1.1
测试创造mod物品
完成了这一步,就可以在游戏中看到我们的mod
这里非常推荐一种mod命名规则:
[{作者名缩写}{支持的版本号}]{mod名称}
例如:[SR1.2]Test Gun
这种规范可以让人一眼了解这个mod是否适用于当前版本
接下来在Defs那个文件夹内创建一个ThingDefs的文件夹,然后在ThingDefs内创建一个xml文件叫做RangedWeapon_TestGun
这是原游戏的命名规则,不遵守也可以正常运行,但我还是推荐认真一点命名。
接下来在xml中更改一下格式。
然后我们要去“抄”一下原游戏中某个枪的xml数据,这样我们才能知道默认都有哪些数据能修改。
我们在vscode中添加一个目录E:\steam\steamapps\common\RimWorld\Data\Core,这是我的游戏目录仅供参考,要填自己电脑安装的目录
core是原游戏的核心,可以发现它的结构和我们mod的定义是完全一样的。打开Defs可以找到所有的物品数据定义,但是太多了反而不知道从哪看起。我们在core中搜索Revolver(手枪),把手枪的数据复制过来
为了方便观察,我加了一些翻译注释
Bullet_Revolver
Things/Projectile/Bullet_Small
Graphic_Single
Bullet
12
1
55
Gun_Revolver
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.
Things/Item/Equipment/WeaponRanged/Revolver
Graphic_Single
1.4
Interact_Revolver
RewardStandardQualitySuper
4000
1.4
0.80
0.75
0.45
0.35
1.6
SimpleGun
Revolver
30
2
3
Verb_Shoot
true
Bullet_Revolver
0.3
25.9
Shot_Revolver
GunTail_Light
9
Blunt
9
2
Blunt
Poke
9
2
到这里为止就可以使用xml修改枪的各种参数,以及发射什么样的子弹(需要去core里再查查都有什么子弹,然后填在defaultProjectile里),还可以更换枪的贴图和音效。更多的物品参数需要查查wiki,然后翻译+自己理解,还有多看看core中的xml代码。
我们本次要制作的是带有瘟疫效果的枪,我们需要创建一种新的子弹,所以我们要在子弹的def里加几个新参数与C#代码交互
在子弹的xml数据中添加
和三个自定义的新属性
0.5
Plague
SR.Projectile_PlagueBullet
然后修改子弹的命名避免和原版一样,修改后如下
Bullet_TestBullet
Things/Projectile/Bullet_Small
Graphic_Single
Bullet
12
1
55
0.5
Plague
SR.Projectile_TestBullet
SR_Gun_TestGun
影兔测试瘟疫效果的枪
Things/Item/Equipment/WeaponRanged/Revolver
Graphic_Single
1.4
Interact_Revolver
RewardStandardQualitySuper
4000
1.4
0.80
0.75
0.45
0.35
1.6
SimpleGun
Revolver
30
2
3
Verb_Shoot
true
Bullet_TestBullet
0.3
25.9
Shot_Revolver
GunTail_Light
9
Blunt
9
2
Blunt
Poke
9
2
为了避免与其他mod命名冲突,我使用SR作为代码的命名空间,SR.XX表示C#类,SR_XX表示xml数据,这也是原游戏默认的命名规范,有两处SR.XX的数据是与C#进行交互的,命名必须一致。
至此xml部分就结束了。
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中。
很遗憾我们的新武器虽然已经有了数据,但是没有任何方法可以在游戏中自然生成,我们需要借助开发者模式来测试我们的新枪支,首先打开开发者模式(记得加载我们的mod),随便建个新地图,然后在上方选择open debug actions menu
在里面找到spawn weapon – SR_Gun_TestGun,然后把它丢在地上,让我们的角色捡起来,然后对着自己的小人开一枪试试,刚好触发了瘟疫效果
未触发的log也正常显示
叠加到100%瘟疫会直接死亡,也是正常的。至此新武器的制作就完成了。
本地化就是指翻译成各种语言,我们把之前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(名称可以随便取,但还是遵循规范).复制默认的语言数据结构
之后添加两个新的key,对应我们上一步设置的key名字
再把中文的翻译写进去
{0}使用测试枪导致{1}感染瘟疫
{0}未触发瘟疫
至此中文版本就完成了,如果要加入英文版的话,就在Luaguages下创建English目录等等,然后创建相同的文件,在值里填入英文对应的文本即可,游戏会根据用户选择的语言自动选择语言数据加载。
源码下载
如果这篇文章对你有帮助,点赞收藏支持一下呗!