接着上一章的内容,我继续讲解一些技巧和装备实现方法。
不要畏惧报错
虽然VS可以为你揪出语法错误,但是很多错误并非语法错误,他们能通过编译但是会在加载的时候报错,还有一些甚至会在运行的时候报错。如图就是一个加载错误
那么首先,遇到这样的错误不要着急,我看见很多新人一报错就要拿去问。其实很多错误都是很低级的,凭借自己的眼睛就能够找出问题所在。那么我就要教你们如何定位问题。
通过这张图我们首先能得出的信息是:该模组加载时发生错误 TemplateMod2
。意味着这是加载的时候报错的,那么原因可能是没有正确的加载资源,加载过程中出现运行错误等等。
接下来我们看这段话:Terraria.ModLoader.Exceptions.MissingResourceException: 未找到预期的资源
。什么叫未找到预期的资源呢?就是说TML按照它的想法去某个路径找资源,但是那个路径里面没有这个资源,那咋办呢?就只能报错了。
接下来就是TML最有意思的一点了
它会猜测你可能犯的错误,看到这里你就应该知道,本来它要找的Items/Armors/ExampleHelmet_Face
这个图片不见了,但是它能找到一个很相近的,或许本来应该找的是Items/Armors/ExampleHelmet_Head
。
那么我们回想一下为啥会找到这个后缀呢?要么是命名空间错了,要么就是我们某个设置出错了,比如这里就是我使用了[AutoloadEquip(EquipType.Face)]
,而不是[AutoloadEquip(EquipType.Head)]
导致的。
所以遇到问题一定要学会根据提示找错误原因,回想一下自己哪一步出问题了,挨个验证猜想。这个过程也就是我们所有的Debug。
套装
套装应该是泰拉瑞亚Mod制作里面,贴图最难画的部分了,我们可以在Items/Armors
文件夹里面一探究竟。
对,这些就是制作一个套装需要的全部文件,而且如果你仔细观察,就会发现所有这些带后缀的图都是帧图,而且量还不少。
不过幸好我不画贴图,不过我们先看看ExampleHelmet.cs是怎么写的吧。相比于饰品来说,套装的改变可能就在于贴图,[AutoloadEquip(EquipType.XXX)]
,以及套装有关的重写函数了。贴图我们只需要参考原版贴图就好了。
在套装/装备效果里面,我们要用UpdateEquip
这个重写函数来施加效果,而不是UpdateAccessory
,因为装备不具备饰品的一些特性。
从名字上看,ExampleHelmet是头盔的类,那么它需要被标记为头盔才能正确执行,于是我们有[AutoloadEquip(EquipType.Head)]
同理,ExampleBreastplate和ExampleLeggings分别被标记为了[AutoloadEquip(EquipType.Body)]
,[AutoloadEquip(EquipType.Legs)]
。
接下来就是两个套装有关的重写函数IsArmorSet
和UpdateArmorSet
。这两个函数的功能我在注释里面已经解释的很清楚了。
但是如果你观察的足够仔细,你会发现我只在ExampleHelmet里面写了套装相关,那么是不是一定要在头盔里面写呢?
public override bool IsArmorSet(Item head, Item body, Item legs) { return body.type == mod.ItemType("ExampleBreastplate") && legs.type == mod.ItemType("ExampleLeggings"); }
仔细看代码,你会发现我只判定了身体是否是ExampleBreastplate
以及腿是不是ExampleLeggings
。我并没有去判定头盔是不是,因为我已经在头盔这个类里面了,所以如果头盔不是ExampleHelmet
这行代码压根就不会执行。
所以不是的,你可以在头盔,胸甲,护腿任意一个类里面写。那么问题来了,如果在三个类里面都写了套装效果会怎么样呢?
药水与Buff
药水的贴图我放在了Items目录下,PoisonousPotion,翻译过来就是有毒的药水(当然,你也可以让它没有毒)。
药水的基本组成与之前的物品没有任何区别(先新建PoisonousPotion.cs),如果你们忘了怎么写,可以回到之前的教程去看,或者直接把之前写的物品复制过来。
接下来我们要为物品设置基础属性(SetDefaults),这里开始不同了
// 这部分就不说了 item.width = 14; item.height = 24; item.useAnimation = 17; item.useTime = 17; item.maxStack = 30; item.rare = 5; item.value = Item.sellPrice(0, 0, 50, 0); // 物品的使用方式,还记得2是什么吗 item.useStyle = 2; // 喝药的声音 item.UseSound = SoundID.Item3; // 决定这个物品使用以后会不会减少,true就是使用后物品会少一个,默认为false item.consumable = true; // 决定使用动画出现后,玩家转身会不会影响动画的方向,true就是会,默认为false item.useTurn = true; // 告诉TR内部系统,这个物品是一个生命药水物品,用于TR系统的特殊目的(比如一键喝药水),默认为false item.potion = true; // 这个药水能给玩家加多少血,跟potion一起使用喝完药就会有抗药性debuff item.healLife = 50; // 加buff的方法1:设置物品的buffType为buff的ID // 这里我设置了中毒debuff(2333 item.buffType = BuffID.Poisoned; // 用于在物品描述上显示buff持续时间 item.buffTime = 60000;
把这些属性设置好,进入游戏喝下这个药水你就会先加血然后中毒而死了。等等,我们是怎么让玩家中毒的呢?Buff,BuffID。或许你已经知道是怎么回事了,没错,原版的Buff也是有ID的,你可以在wiki找到对于每种Buff的BuffID: https://terraria-zh.gamepedia.com/%E5%A2%9E%E7%9B%8A_ID 。
但是就目前来看,我们的药水只能增加一种buff,如果我们想要加多种buff怎么办?这时候就要有请UseItem
重写函数了。
// 当物品使用的时候触发,返回值貌似是什么都不会有影响 public override bool UseItem(Player player) { // 给玩家加上中毒buff,持续 60000 / 60 = 1000秒 // 第一个填buff的ID,第二个填持续时间 player.AddBuff(BuffID.Poisoned, 60000); // 给玩家加上猛毒buff,持续 60000 / 60 = 1000秒 player.AddBuff(BuffID.Venom, 60000); return false; }
player.AddBuff
是一个非常强大的函数,可以给玩家添加buff,我们来仔细看一下这个函数怎么用。它的原型是这样的
public void AddBuff(int type, int time1, bool quiet = true);
第一个type
想必就是buff的id了,第二个time1
是buff的持续时间,至于这个quiet
我们会在第四部分进行讲解。那么根据这个我们就可以使用这个函数给玩家添加buff了。值得一提的是,给NPC加buff也是用这个函数。
原版的中毒buff貌似不太给力啊,不如我们来自定义一个怎么样。
首先我们要新建一个叫Buffs的文件夹,用来存放Buff的代码和图片(此时的命名空间应该是什么?)
然后我们找个buff的贴图来,就像这样的
然后按照流程创建好cs文件,只不过这次我们的类需要继承ModBuff
,告诉TML这个类是一个Mod的Buff。然后我们加入ModBuff特有的重写函数SetDefaults
和Update
,弄好后大概是这样:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using TemplateMod2.Utils; using Terraria; using Terraria.ModLoader; namespace TemplateMod2.Buffs { public class SuperToxic : ModBuff { public override void SetDefaults() { } // 注意这里我们选择的是对Player生效的Update,另一个是对NPC生效的Update public override void Update(Player player, ref int buffIndex) { } public override void Update(NPC npc, ref int buffIndex) { base.Update(npc, ref buffIndex); } } }
对于SetDefaults
这个函数,我们可以这么设置
// 设置buff名字和描述 DisplayName.SetDefault("剧毒"); Description.SetDefault("你中毒了,祝你好运"); // 因为buff严格意义上不是一个TR里面自定义的数据类型,所以没有像buff.XXXX这样的设置属性方式了 // 我们需要用另外一种方式设置属性 // 这个属性决定buff在游戏退出再进来后会不会仍然持续,true就是不会,false就是会 Main.buffNoSave[Type] = true; // 用来判定这个buff算不算一个debuff,如果设置为true会得到TR里对于debuff的限制,比如无法取消 Main.debuff[Type] = false; // 当然你也可以用这个属性让这个buff即使不是debuff也不能取消,设为false就是不能取消了 this.canBeCleared = false; // 决定这个buff是不是照明宠物的buff,以后讲宠物和召唤物的时候会用到的,现在先设为false Main.lightPet[Type] = false; // 决定这个buff会不会显示持续时间,false就是会显示,true就是不会显示,一般宠物buff都不会显示 Main.buffNoTimeDisplay[Type] = false; // 决定这个buff在专家模式会不会持续时间加长,false是不会,true是会 this.longerExpertDebuff = false; // 如果这个属性为true,pvp的时候就可以给对手加上这个debuff/buff Main.pvpBuff[Type] = true; // 决定这个buff是不是一个装饰性宠物,用来判定的,比如消除buff的时候不会消除它 Main.vanityPet[Type] = false;
差不多就这么多,其实实际用途上不会用到这么多属性,比如debuff只用设置noSave和debuff就行了,但是我都写下来了给你们作参考。毕竟Update
里面写的东西才是主题。
既然是剧毒,那么我们就要让玩家持续掉血,还记得上一章的player.lifeRegen
吗,它能让玩家持续回血,但是如果我们把它设为负数呢?玩家就掉血了!
但是一般来说,debuff不会直接把player.lifeRegen
直接设为一个负数,为什么呢?假设有好几个debuff都把player.lifeRegen
设为了不同的值,那么玩家的最终减血速率会是哪个呢?按理来说应该是叠加的,但是事实上可能是任何一个debuff的单独速率,而且我们并不能确定是哪个。也就是说,无论你身上有多少debuff一秒都只减20滴血,就达不到多个debuff同时杀伤的能力了。
所以事实上我们需要这么写debuff效果:
public override void Update(Player player, ref int buffIndex) { // 把玩家的所有生命回复清除,否则可能会把debuff效果抵消掉 if (player.lifeRegen > 0) { player.lifeRegen = 0; } player.lifeRegenTime = 0; // 让玩家的减血速率增加40 player.lifeRegen -= 40; }
然后我们在喝药的时候加上这么一句
// 如果这句话被VS画红线了你们应该知道怎么做吧? player.AddBuff(ModContent.BuffType<SuperToxic>(), 60000);
进入游戏,喝药,你就会发现自己血量正在飞速往下掉。
除了Update
以外,我们还有一个很好玩的函数ReApply
,它是发生在玩家在已经有这个buff的情况下仍然被AddBuff了的时候
// 如果返回true就代表buff重新添加成功 public override bool ReApply(Player player, int time, int buffIndex) { // 这段代码的作用就是当玩家被重新添加buff的时候延长buff时间 player.buffTime[buffIndex] += time; return true; }
自定义Buff的知识到此就介绍完了,但是实际上这些都是很基础的知识,完全没有发挥出他们强大的功能。不多等到我们学的知识越来越多的时候,我们就知道应该如何利用他们了。下一章我们将会介绍更多有用的重写函数以及添加特效的方法。
如果这章有不理解的地方或者建议欢迎向我提出,第一部分的所有代码将会在下一章结束后发到github上,可以参考。
练习
基础
- 实现原版神圣套的所有属性(包括三种头盔)答案首先,我们要明确我们需要3个头盔、一个胸甲和一个护腿。
除此之外,我们一定要把套装效果写在三个不同的头盔里面,因为每个头盔都会带来不同的效果。
至于属性的设置,就参考这一章和上一章的内容了,这里不再多说。 - 如何用buff让玩家发光?答案发光函数可以这么写
// 这里是按照RGB的比例(0, 1, 0)在玩家中心发出绿光 // 值越大发的光越强 Lighting.AddLight(player.Center, 0, 1.0f, 0f);
- 如何用buff让玩家减血速率一开始很快,buff结束的时候变慢?答案参考实现
// 让玩家的减血速率随着时间而减少 // player.buffTime[buffIndex]就是这个buff的剩余时间 // 注意不要把buff时间设置的太长,否则不好观察效果 player.lifeRegen -= player.buffTime[buffIndex];
- 如何做一个永远不会结束的buff答案在
SetDefaults
里面// 为什么我这么设置? Main.buffNoTimeDisplay[Type] = true; this.canBeCleared = false; Main.debuff[Type] = true; Main.buffNoSave[Type] = false;
在
Update
里面player.buffTime[buffIndex] = 2;
当然,你死了还是会结束的QAQ,如果死了也不结束的话……
进阶
EquipType
都可以设置为哪些值?他们各自代表了什么样的装备,他们的帧图有什么特点?buffIndex
这个东西是干什么的,改了它会发生什么?能不能在Update
里删掉自己。- 如果要修改原版的套装特效,应该怎么做?
- 做个3种头盔,3种胸甲,3种护腿的套装吧,总共要有\(3 \times 3 \times 3 = 27\)种套装效果哦(笑)。
- 实现一个“战争热诚“效果:当近战攻击敌人积累层数,每层增加近战攻速5%,一段时间不攻击敌人后层数掉光。
裙子加油!
!等到更新了!
大佬,那个给药水添加多种buff的重写,返回值是false的话药水不会被消耗,写成true才好
请问我以这个作为起点,后来我想改这个mod的名字,怎么办
关于ModBuff
1 每个ModBuff只有一个实例在加载mod时产生,所以试图在ModBuff中为每个npc单独存储数值如A:ModBuff{int B;
…npc.lifeRegen-=B;…}是没用的,实际上所有该Buff会共用同一个B
2 ReApply中time与npc.AddBuff中time都可以是负数
3 像魔力疾病一样根据buff的时间等改变buff的描述时,只需要在ModifyBuffTip中考虑Main.MyPlayer即可,因为buff的描述只有Main.MyPlayer会看到自己的(魔力疾病也是这么做的:
buffString = Lang.GetBuffDescription(bufftype);
if (bufftype== 94)
{
buffString = string.Concat(buffString,((int)(Main.player[myPlayer].manaSickReduction * 100f) + 1).ToString(),”%”);
}
)
buff的描述只有Main.MyPlayer会看到自己的
指
每个端只会看到自己即Main.MyPlayer的buff描述
关于ModBuff
1 每个ModBuff只有一个实例在加载mod时产生,所以试图在ModBuff中为每个npc单独存储数值如A:ModBuff{int B;
…npc.lifeRegen-=B;…}是没用的,实际上所有该Buff会共用同一个B
2 ReApply中time与npc.AddBuff中time都可以是负数
3 像魔力疾病一样根据buff的时间等改变buff的描述时,只需要在ModifyBuffTip中考虑Main.MyPlayer即可,因为每个端只会看到自己即Main.MyPlayer的buff描述(魔力疾病也是这么做的:
buffString = Lang.GetBuffDescription(bufftype);
if (bufftype== 94)
{
buffString = string.Concat(buffString,((int)(Main.player[myPlayer].manaSickReduction * 100f) + 1).ToString(),”%”);
}
)
产生了奇怪的想法就是。。
buff是否可以在/判断玩家受伤/和/实际造成伤害/两个事件之间做些事。。
比如,/如果 受到伤害 低于 最大生命值 的 百分之五 就 播放音效+免疫本次伤害+释放一圈类似血荆棘的长刺/
后面这一串操作其实很好做到,但是判断受伤和免疫。。。。暂时不明白怎么写
可以让buff设置玩家的属性,然后在
ModPlayer
里进行这些操作,所有你说的这些都是ModPlayer
的重写函数是否