Hello World

  • 欢迎来到泰拉瑞亚的创意无限世界,在这里,不仅仅是探索与冒险,更是创造与实现梦想的天地。作为一名勇敢的开发者,你即将他上的旅程,不近视深入这个像素世界的腹地,更是要在其基础上构建属于你自己的奇迹。本指南将是你受众的罗盘,引领你步入泰拉瑞亚模组开发的神秘大门。
  • 想象一下,当你手持代码之间,桥下第一个字符,一个简单的"Hello World"便能在泰拉瑞亚广阔的天地间回响,这不仅是变成传统的直径,也是你成为游戏世界造物主的第一步。泰拉瑞亚mod开发的魅力,在于它赋予你重塑世界规则的能力,无论是新奇的武器、独特的怪物,还是整个异世界的创造,一切皆有可能。
  • 本指南将循序渐进,从最基础的设置环境开始,逐步深入如何使用C#和TModLoader来编写你的第一个mod,理解背后的逻辑与技巧,为你后续的创意开发打下坚实的基础。
  • 无论你是编程新手,还是经验丰富的开发者初涉游戏领域,这篇指南都力求让你的每一步学习都充满乐趣与成就感。准备好了吗?让我们异同步入泰拉瑞亚的编程仙境,用一行行代码编制出属于你的传奇故事。
  • 让我们开始吧!

Mod制作准备

  1. 一台电脑
  2. 玩过泰拉瑞亚,原版或者灾厄至少通关过一次(博主原版大师、灾厄死亡大师都通关了)
  3. 安装IDE,强烈建议VSCode,主要是写C#
  4. 会使用画图软件(Photoshop等)

.NET环境安装

  • 开发环境的安装,可以直接看微软官方教程
  • 这里再建议安装一个通义灵码的插件,用起来还是挺方便的

TModLoader的介绍与安装

源码Mod和TML Mod的区别

  • 源码Mod的制作方法是通过反编译泰拉瑞亚源码,并在此基础上修改而制作的模组。优点是自由度高,可以实现的功能多。缺点是很难管理,灵活度低下,尤其是当Mod规模扩大的时候。
  • TML Mod通过提供原版的接口使得开发者可以在一个与原版独立的环境开发Mod,也就是说,开发者可以不用关注原版冗长的代码实现细节,而是与接口进行互动。优点是管理方便、灵活。缺点是实现的功能具有局限性,有时候需要等待TML开发者添加了某些接口才能实现特定的功能。但是随着TML这么多年功能的不断的完善,以及原版泰拉瑞亚代码越来越晦涩难懂,TML开发最终占据了主导。

TModLoader的安装

  • 如果你之前打过mod玩,那么其实你已经安装好了,对吧

  • 如果你是第一次使用mod游玩,直接在Steam上下载安装即可

  • 安装完成之后,直接TMOD,启动!

  • 点击创意工坊,进入创意工坊中心

  • 点击开发模组,会看到如下页面

第一个Mod

  • 点击右下角创建模组,填写模组信息我们就可以初始化一个模组项目了

    • ModName:模组名称,不要夹杂空格
    • Mod DisplayName:模组显示名称
    • Mod Author:模组作者
    • BasicSword:初始之剑,这里填写的是剑的名称,创建项目时,会自动生成这把剑的初始化代码
  • 点击创建后,初始化的项目如下

  • 我们在VSCode里打开这个项目,首先看一下目录结构

    1. [ModName].cs:这是Mod类。它是任何Mod的核心文件,每个Mod只能存在一个Mod类,对于简单的Mod,这个文件会非常简洁,但是这个类中可以发生各种全局性的事情。
    2. description.txt:包含Mod的描述文本,在Mod菜单中点击“更多信息”按钮可以在游戏中查看。如果你愿意,可以创建一个带有额外BBCode格式的description_workshop.txt文件,该文件将在Steam创意工坊网站上显示。
    3. build.txt:包含Mod的版本、作者和显示信息。可以包含其他值。这个文件是必要的。
    4. icon.png:在游戏中显示的80x80图标。你可以为Steam创意工坊创建一个更详细或更高分辨率的icon_workshop.png图标,该文件可以达到512x512像素。
    5. [ModName].csproj:为Visual Studio设置的项目文件,用于调试你的Mod。调试非常有用,但需要一些学习,不要删除它。
    6. Properties/launchSettings.json:与ModName.csproj相关,包含用于调试tModLoader文件路径,不要删除,随着你的经验积累,它会变得很有用。
    7. Content/Items/[ItemName].cs:一个简单的剑物品。以此作为示例,学习如何制作其他ModItem类。
    8. Content/Items/[ItemName].png:对应的物品图像。
    9. Localization/en-US_Mods.[ModName].hjson:包含Mod中内容的英文文本。它目前包含生成的剑的显示名称和工具提示。这个文件会随着你向Mod添加新内容而自动更新条目。请参阅Localization Wiki了解更多本地化及支持其他语言的信息。
  • 下面我们直接来看初始化剑的代码,以及对应的配置文件信息。

  • 下面这段代码就是初始之剑的代码,我们先来看SetDefaults()方法,这个方法就是初始化物品的配置信息,这里我们配置了物品的伤害、攻击类型、宽高、使用时间、使用动画、使用方式、击退、价值、稀有度、音效、自动使用(近战武器自动挥舞)等。
  • 同时我们需要关注AddRecipes()方法,这个方法就是添加物品的合成配方,这里我们添加了合成配方,需要用10个土块,并且需要工作台才可以制作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;

namespace MyFirstMod.Content.Items
{
// This is a basic item template.
// Please see tModLoader's ExampleMod for every other example:
// https://github.com/tModLoader/tModLoader/tree/stable/ExampleMod
public class Excalibur : ModItem
{
// The Display Name and Tooltip of this item can be edited in the 'Localization/en-US_Mods.MyFirstMod.hjson' file.
public override void SetDefaults() {
Item.damage = 50;
Item.DamageType = DamageClass.Melee;
Item.width = 40;
Item.height = 40;
Item.useTime = 20;
Item.useAnimation = 20;
Item.useStyle = ItemUseStyleID.Swing;
Item.knockBack = 6;
Item.value = Item.buyPrice(silver: 1);
Item.rare = ItemRarityID.Blue;
Item.UseSound = SoundID.Item1;
Item.autoReuse = true;
}

public override void AddRecipes() {
Recipe recipe = CreateRecipe();
recipe.AddIngredient(ItemID.DirtBlock, 10);
recipe.AddTile(TileID.WorkBenches);
recipe.Register();
}
}
}
  • 那我们现在加载Mod,并且进入游戏
  • 建造一个工作台,并且使用10个土块,即可制造我们的初始之剑
  • 50点伤害有点不够看啊,那我们现在修改一下武器伤害以及攻击间隔,随后重新构建并加载我们的Mod,然后再次进入游戏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;

namespace MyFirstMod.Content.Items
{
public class Excalibur : ModItem
{
public override void SetDefaults() {
- Item.damage = 50;
+ Item.damage = 99999999;
Item.DamageType = DamageClass.Melee;
Item.width = 40;
Item.height = 40;
- Item.useTime = 20;
- Item.useAnimation = 20;
+ Item.useTime = 5;
+ Item.useAnimation = 5;
Item.useStyle = ItemUseStyleID.Swing;
Item.knockBack = 6;
Item.value = Item.buyPrice(silver: 1);
Item.rare = ItemRarityID.Blue;
Item.UseSound = SoundID.Item1;
Item.autoReuse = true;
}

public override void AddRecipes() {
Recipe recipe = CreateRecipe();
recipe.AddIngredient(ItemID.DirtBlock, 10);
recipe.AddTile(TileID.WorkBenches);
recipe.Register();
}
}
}

  • 在这里可以配置Mod物品的显示名称和提示信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
# This file will automatically update with entries for new content after a build and reload.

Items: {
Excalibur: {
DisplayName: "Excalibur"
Tooltip:
'''
Template of an item
Replacing this tooltip is duty
It's snowing on Mt.Fuji
'''
}
}
  • 修改初始之剑的名称以及描述
1
2
3
4
5
6
Items: {
Excalibur: {
DisplayName: Hello World!
Tooltip: 代码之力,小子!
}
}

Basic Item

  • 本小节旨在解释所有Item之间的共同点。

什么是物品(What is an Item)?

  • 了解物品(Item)、抛射物(Projectiles)和方块(Tiles)之间的区别非常重要。刚开始时,如果你不清楚它们的不同之处,可能会无意中混淆概念。例如,有些人可能会混淆并试图将工作台物品(Item)添加到配方中,而实际上他们想要添加的是工作台方块(Tile)。还需要意识到,像回旋镖武器这样的东西实际上由物品和抛射物组成。虽然这是一个简单的概念,但请记住这一点。

制作物品(Making an Item)

  • 要将物品添加到泰拉瑞亚,我们首先必须创建一个类,该类需要继承ModItem,实际上上面小节中的[ModName].cs就是个样例,在创建Mod的时候已经为我们生成了一把剑的物品。

设置默认值

  • 物品最重要的部分是 SetDefaultsSetDefaults 是设置物品属性的地方,例如物品使用什么弹药、物品的大小以及物品放置的是哪个方块。请参阅 Item 类文档 ,了解在 SetDefaults 中常见的设置值的含义。你还可以通过访问 Vanilla Item 来查看原版物品字段值。

本地化

  • 构建你的 Mod 后,本地化文件通常位于 My Games\Terraria\tModLoader\ModSources\MyModName\Localization\en-US_Mods.MyModName.hjson,将会填充有新添加物品的条目。对于物品,你会看到 DisplayName 和 Tooltip 的条目。DisplayName 将默认为类名,单词之间用大写字母分隔。Tooltip 默认为空。你可以编辑此文件以自定义这些本地化内容:
1
2
3
4
5
6
Items: {
NameHere: {
DisplayName: Name Here
Tooltip: This is a modded item.
}
}

Basic Projectile

  • 在学习向量以及计算几何的知识之前,最好能先创建一个自己的弹幕。这样我们可以通过这个弹幕观察到数学是如何发挥作用的。

弹幕基础

什么是Projectile

  • 在开始修改弹幕之前,我们需要先了解物品和弹幕之间的区别。物品是可以存储在库存中(例如背包、箱子等)的对象,而弹幕时从武器或者敌人射出的对象,例如子弹、箭等。

什么情况下会使用弹幕?

  • 在泰拉瑞亚中,许多物品因为弹幕的存在而具有功能性,例如枪和弓(子弹和箭)、激光、炸弹和其他投掷物品,以及大多数魔法武器,甚至一些抓钩、连枷、长矛、宠物、召唤物、钻头和悠悠球,也会生成弹幕。很多敌怪也可以生成弹幕。

创建基本弹幕

  • 在创建弹幕之前,我们最好先规范一下目录结构,为后续所有的弹幕建立一个单独的文件夹,名为Porjectiles;同时也为刚刚的剑创建一个单独的文件夹,名为Sowrds。
  • 随后在Projectiles目录下创建[ProjectileName].cs[ProjectileName].png(弹幕贴图)文件。创建弹幕时,我们需要继承ModProjectile类,同时需要实现SetDefaults方法,SetDefaults方法中需要设置弹幕的属性,例如弹幕的大小、弹幕的速度、弹幕的伤害、弹幕的伤害类型等。
  • 下面代码中的namespace需要替换为你自己的模组文件夹名/命名空间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;

namespace MyFirstMod.Content.Items.Projectiles
{
public override void SetDefaults()
{
Projectile.arrow = true;
Projectile.width = 10;
Projectile.height = 10;
Projectile.aiStyle = ProjAIStyleID.Arrow;
Projectile.friendly = true;
Projectile.DamageType = DamageClass.Ranged;
AIType = ProjectileID.WoodenArrowFriendly;
}

// Additional hooks/methods here.
}

应用弹幕

  • 请记住,物品和弹幕是不同的。一个常见的错误是,模组开发者创建了一个弹幕,却不了解还需要创建一个使用该弹幕的东西。例如,对于投掷飞刀武器,您需要同时创建一个物品和一个弹幕。弹药物品也需要有一个独特的弹幕与之关联。如果弹幕是由NPC生成的,您不总是需要同时创建物品和弹幕。测试弹幕的最简单方法是创建一个物品,并将 Item.shoot 设置为弹幕。
    1
    Item.shoot = ModContent.ProjectileType<MyProjectile>();
  • 在刚刚我们已将创建好了一个弹幕,现在我们将其作用于武器上
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    + using MyFirstMod.Content.Items.Projectiles;
    using Terraria;
    using Terraria.ID;
    using Terraria.ModLoader;

    namespace MyFirstMod.Content.Items.Swords
    {
    public class Excalibur : ModItem
    {
    public override void SetDefaults() {
    Item.damage = 99999999;
    Item.DamageType = DamageClass.Melee;
    Item.width = 40;
    Item.height = 40;
    Item.useTime = 5;
    Item.useAnimation = 5;
    Item.useStyle = ItemUseStyleID.Swing;
    Item.knockBack = 6;
    Item.value = Item.buyPrice(silver: 1);
    Item.rare = ItemRarityID.Blue;
    Item.UseSound = SoundID.Item1;
    Item.autoReuse = true;
    + Item.shoot = ModContent.ProjectileType<MyFirstProjectile>();
    }

    public override void AddRecipes() {
    Recipe recipe = CreateRecipe();
    recipe.AddIngredient(ItemID.DirtBlock, 10);
    recipe.AddTile(TileID.WorkBenches);
    recipe.Register();
    }
    }
    }
  • 重新编译并加载Mod,进入游戏,挥舞武器,可以看到我们刚刚制作的弹幕已经生成了。

SetDefaults

  • 弹幕最重要的部分是SetDefaults方法。该方法是设置弹幕值的地方,例如命中宽度和高度、弹幕是友军还是敌军、以及弹幕将使用哪个AI。请参阅弹幕类文档以了解通常设置的值的含义。还可以通过放温暖Vanilla弹幕字段值来查看Vanilla弹幕值。

Projectile.Damage

  • 一个常见的错误是在SetDefaults中设置Projectiles.Damage,这不起作用,因为弹幕的伤害值总是被生成弹幕时传递给Projectile.NewProjectile的值覆盖。通常,生成弹幕的物品或NPC将决定伤害值。

Other Hooks/Methods

  • ModProjectile文档列出了许多其他钩子/方法,您可以使用它们生成独特的弹幕。例如在弹幕击中敌人时施加减益效果,可以使用OnHitNpc方法,要在弹幕击中物块时做些事情,可以使用OnTileCollide方法。

What is AI

  • 弹幕的AI是弹幕最重要的方面,它控制弹幕在生成后如何移动和行动,对于新手模组开发者来说,最简单的方法是通过设置Projectile.aiStyle = #AIType = ProjectileID.NameHere来一来其他原版弹幕已经使用的AI代码。你分配给Projectile.aiStyle的值应该与ProjectileID.NameHere的值相匹配。这杯成为模仿原版弹幕。
  • 随着你对更高级的弹幕移动行为的需求,你会发现模仿原版弹幕AI非常有限,所以下面我们将讨论如何自定义AI。

使用原版AI

  • 我们可以使用原版AI来初始化我们的弹幕。让我们制作一个回旋镖,使用和原版回旋镖弹幕相同的aiStyle。我们可以在中查找回旋镖弹幕,可以看到原版回旋镖使用的aiStyle为3。
  • 那么我们可以在代码中使用Projectile.aiStyle = 3;,或者将3改为ProjectileID.Boomerang,增强代码的可读性。
  • 为了让这个回旋镖更简单,我们可以使用Projectile.CloneDefaults(ProjectileID.EnchantedBoomerang);,这将复制所有其他的默认值。这样,你将获得一个与原版回旋镖行为相同的弹幕。
  • 通过设置AIType可以改变弹幕的粒子特效。
  • 请注意,武器贴图请自行解决。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using MyFirstMod.Content.Items.Projectiles;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;

namespace MyFirstMod.Content.Items.Boomerangs
{
public class MyFirstBoomerang : ModItem
{
public override void SetDefaults() {
Item.damage = 99999999;
Item.DamageType = DamageClass.Melee;
Item.width = 40;
Item.height = 40;
Item.useTime = 5;
Item.useAnimation = 5;
Item.useStyle = ItemUseStyleID.Swing;
Item.knockBack = 6;
Item.value = Item.buyPrice(silver: 1);
Item.rare = ItemRarityID.Blue;
Item.UseSound = SoundID.Item1;
Item.autoReuse = true;
// 重点是这一行,指定弹幕类型
Item.shoot = ModContent.ProjectileType<BoomerangProjectileDemo>();
Item.shootSpeed = 10f;
}

public override void AddRecipes() {
Recipe recipe = CreateRecipe();
recipe.AddIngredient(ItemID.DirtBlock, 10);
recipe.AddTile(TileID.WorkBenches);
recipe.Register();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;

namespace MyFirstMod.Content.Items.Projectiles
{
public class BoomerangProjectileDemo : ModProjectile
{
public override void SetDefaults()
{
Projectile.width = 10;
Projectile.height = 10;
Projectile.aiStyle = ProjAIStyleID.Boomerang;
Projectile.friendly = true;
Projectile.DamageType = DamageClass.Ranged;
AIType = ProjectileID.WoodenArrowFriendly;
}

// Additional hooks/methods here.
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;

namespace MyFirstMod.Content.Items.Projectiles
{
public class BoomerangProjectileDemo : ModProjectile
{
public override void SetDefaults()
{
Projectile.CloneDefaults(ProjectileID.EnchantedBoomerang);
AIType = ProjectileID.EnchantedBoomerang;
}

// Additional hooks/methods here.
}
}
  • 重新编译并加载Mod,进入游戏,我们就可以使用10个土块在工作台旁制作我们刚刚编写的回旋镖了。亲测手感和原版几乎无异(虽然我也不怎么用回旋镖武器就是了)。

自定义AI

  • 本小节将讨论你可以在AI中添加的元素,记得如果使用Projectile.CloneDefaults复制其他弹幕默认值,要将Projectile.aiStyle的值设回0。所有自定义AI的代码都放在ModProjectile.AI方法中。也就是重写AI()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;

namespace MyFirstMod.Content.Items.Projectiles
{
public class MyFirstProjectile : ModProjectile
{
public override void SetDefaults()
{
Projectile.arrow = true;
Projectile.width = 10;
Projectile.height = 10;
Projectile.aiStyle = ProjAIStyleID.Arrow;
Projectile.friendly = true;
Projectile.DamageType = DamageClass.Ranged;
AIType = ProjectileID.WoodenArrowFriendly;
}

// Additional hooks/methods here.

// 重写此方法,自定义AI
public override void AI()
{
base.AI();
// Additional code here.
}
}
}

Timers

  • Timers:计时器
  • 许多弹幕使用计时器来延迟动作。通常我们使用Projectile.ai[0]Projectile.ai[1],因为这些值会自动同步,这里我们计数到30,换句话说,半秒。
1
2
3
4
5
6
7
8
Projectile.ai[0] += 1f;
if (Projectile.ai[0] >= 30f)
{
// 半秒过去了。重置计时器等。
Projectile.ai[0] = 0f;
Projectile.netUpdate = true;
// 在这里做点什么,也许改变到新状态。
}

Gravity

  • Gravity:重力
  • 弹幕实际上没有重力,每个带有重力移动的弹幕实际上是它们的AI代码实现了重力。要实现重力,只需要在Projectile.velovity.Y中添加一个小值。
1
2
3
4
5
Projectile.velocity.Y = Projectile.velocity.Y + 0.1f; // 0.1f 是箭的重力,0.4f 是飞刀的重力
if (Projectile.velocity.Y > 16f) // 这个检查实现了“终端速度”。我们不希望弹幕越来越快。
{
Projectile.velocity.Y = 16f;
}
  • 根据上述的计时器和重力的设定,我们现在可以写一个延迟重力效果
1
2
3
4
5
6
7
8
9
10
Projectile.ai[0] += 1f; // 使用计时器等待15个刻度后再应用重力。
if (Projectile.ai[0] >= 15f)
{
Projectile.ai[0] = 15f;
Projectile.velocity.Y = Projectile.velocity.Y + 0.1f;
}
if (Projectile.velocity.Y > 16f) // 终端速度检查,设置弹幕速度不超过16像素/帧。
{
Projectile.velocity.Y = 16f;
}

Wind Resistance

  • Wind Resistance:风阻
  • 纵向的加速度是重力控制的,那么横向的加速度是风阻控制的。
  • 通过减少Projectile.velocity.X的乘积系数,我们可以轻松实现风阻。结合计时器,我们同样可以让弹幕越来越慢,实现类似蜜蜂手雷的效果。(打肉山的时候,在平台扔蜜蜂手雷,一开始速度蛮快的,但是会越来越慢,直至爆炸)
1
2
3
4
5
Projectile.ai[0] += 1f;
if (Projectile.ai[0] >= 15f) {
// 这里的示例没有加上重力,无伤大雅
Projectile.velocity.X = Projectile.velocity.X * 0.97f;
}

Rotation

  • Rotation:旋转

恒定旋转

  • 我们可以在AI中添加Projectile.rotation使其想回旋镖一样旋转。
1
Projectile.rotation += 0.4f * Projectile.direction;

面向前方

自定义坐骑