跳至正文

自定义套装和Buff

接着上一章的内容,我继续讲解一些技巧和装备实现方法。


不要畏惧报错

虽然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)]

同理,ExampleBreastplateExampleLeggings分别被标记为了[AutoloadEquip(EquipType.Body)][AutoloadEquip(EquipType.Legs)]

接下来就是两个套装有关的重写函数IsArmorSetUpdateArmorSet。这两个函数的功能我在注释里面已经解释的很清楚了。

但是如果你观察的足够仔细,你会发现我只在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特有的重写函数SetDefaultsUpdate,弄好后大概是这样:

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上,可以参考。


练习

基础

  1. 实现原版神圣套的所有属性(包括三种头盔)答案
    首先,我们要明确我们需要3个头盔、一个胸甲和一个护腿。
    除此之外,我们一定要把套装效果写在三个不同的头盔里面,因为每个头盔都会带来不同的效果。
    至于属性的设置,就参考这一章和上一章的内容了,这里不再多说。
  2. 如何用buff让玩家发光?答案
    发光函数可以这么写
    // 这里是按照RGB的比例(0, 1, 0)在玩家中心发出绿光
    // 值越大发的光越强
    Lighting.AddLight(player.Center, 0, 1.0f, 0f);
    
  3. 如何用buff让玩家减血速率一开始很快,buff结束的时候变慢?答案
    参考实现
    // 让玩家的减血速率随着时间而减少
    // player.buffTime[buffIndex]就是这个buff的剩余时间
    // 注意不要把buff时间设置的太长,否则不好观察效果
    player.lifeRegen -= player.buffTime[buffIndex];
    
  4. 如何做一个永远不会结束的buff答案
    SetDefaults里面
    // 为什么我这么设置?
    Main.buffNoTimeDisplay[Type] = true;
    this.canBeCleared = false;
    Main.debuff[Type] = true;
    Main.buffNoSave[Type] = false;

    Update里面

    player.buffTime[buffIndex] = 2;

    当然,你死了还是会结束的QAQ,如果死了也不结束的话……

进阶

  1. EquipType都可以设置为哪些值?他们各自代表了什么样的装备,他们的帧图有什么特点?
  2. buffIndex这个东西是干什么的,改了它会发生什么?能不能在Update里删掉自己。
  3. 如果要修改原版的套装特效,应该怎么做?
  4. 做个3种头盔,3种胸甲,3种护腿的套装吧,总共要有\(3 \times 3 \times 3 = 27\)种套装效果哦(笑)。
  5. 实现一个“战争热诚“效果:当近战攻击敌人积累层数,每层增加近战攻速5%,一段时间不攻击敌人后层数掉光。

《自定义套装和Buff》有10个想法

  1. 关于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(),”%”);
    }
    )

  2. 关于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(),”%”);
    }
    )

  3. 产生了奇怪的想法就是。。
    buff是否可以在/判断玩家受伤/和/实际造成伤害/两个事件之间做些事。。
    比如,/如果 受到伤害 低于 最大生命值 的 百分之五 就 播放音效+免疫本次伤害+释放一圈类似血荆棘的长刺/

    后面这一串操作其实很好做到,但是判断受伤和免疫。。。。暂时不明白怎么写

发表回复