跳至正文

给物品加特技

终于来到了第一部分的末尾,这篇教程我将会介绍关于物品的一些常用重写函数以及基本粒子特效的使用。


如何查找弹幕/物品/Buff ID?

很多人问我XXXID里面每个物品、弹幕、Buff什么的ID是如何寻找的。最好的方式就是玩英文版的TR,这样你就会对每个物品的英文名烂熟于心了,就可以愉快的开始写代码了。

比如说我想知道凝胶的ID,那么如果你英语比较好,应该能知道凝胶的英文是Gel,那么在VS里面你直接ItemID.Gel就好了,VS也会给你自动提示:

但是如果你英语不好怎么办呢?别废话了,快去学吧

但是我为什么推荐使用ItemID.XXXX这种格式呢,因为如果你以后看到一串数字,你还想得起哪个数字对应哪个物品吗,看英文是不是就能对应上了?

人类对于语言 (有意义的语句) 的理解大部分情况下是比对数字的认知更好的。除此之外,它的另一个好处是如果(只是如果)物品的ID在以后的版本被改了,写数字的是不是也得跟着改,但是写ItemID.XXXX的就不用了,因为那里面的值也会跟着变。


粒子特效

尽管我们拥有了一把剑,一把枪和其他奇奇怪怪的东西,但是目前来说,这些武器还是太单调了。如果没有特效那么武器就没有灵魂,那么我们应该如何给武器注入灵魂呢?

这时候就要用到一个泰拉瑞亚自带的特殊系统了:粒子系统

粒子系统是游戏领域一个重要的技术,游戏内的特效靠着粒子系统撑起了半边天。粒子系统表示计算机图形学中模拟一些特定的模糊现象的技术,而这些现象具有用其它传统的渲染技术难以实现的真实感。

在开始介绍粒子系统之前,我们先试一下给模板剑添加一些挥动时的粒子。我们需要用到的重写函数是物品的MeleeEffects

public override void MeleeEffects(Player player, Rectangle hitbox) {
    base.MeleeEffects(player, hitbox);
}

MeleeEffects会在近战武器挥动的时候被出发,它拥有两个参数,一个是玩家一个是这个物品挥动时的判定区域。然后我们加入这样的代码

public override void MeleeEffects(Player player, Rectangle hitbox) {
    // 在武器的挥动判定区域添加一些火焰粒子特效
    Dust.NewDustDirect(hitbox.TopLeft(), hitbox.Width, hitbox.Height,
        MyDustId.Fire, 0, 0, 100, Color.White, 2f);
}

教程中的粒子特效没有特殊情况都会使用Dust.NewDustDirect,它的函数原型是这样的

public static Dust NewDustDirect(Vector2 Position, int Width, int Height, int Type, float SpeedX = 0, float SpeedY = 0, int Alpha = 0, Color newColor = default, float Scale = 1);

可以看到,这个函数的参数非常多,但是不要怕,我们一一来解读,第一个Position就是Dust释放的初始位置,WidthHeight就是粒子释放区域的宽和高,如果这个区域越大粒子就会散布的越大。

Type当然就是粒子的ID了,至此我们又多了一种ID,但是你有没有注意到刚才那段代码的MyDustId被VS画上了红线?

利用小灯泡解决问题后你会发现多了一行using TemplateMod2.UtilsMyDustId其实就是在TemplateMod2/Utils文件夹下的一个文件,是本教程专门提供的粒子ID表查询文件。你可以利用它以及VS帮你快速寻找到合适的Dust使用。

接下来两个SpeedXSpeedY,分别是粒子速度的x,y分量,如果不理解没关系,第二部分我们会重点讲。Alpha参数是粒子透明度,从0到255,越大越透明。newColor就是把粒子成什么颜色,注意这里我用的是染,意味着颜色会被直接叠加到dust上。大多数时候,这样的颜色叠加效果都很差,所以一般都用Color.White,即不染色。

最后的Scale参数就是粒子的缩放倍数,1就是保持正常,2就是两倍大小,可以是小数。

尝试着修改这些参数,看看在游戏中效果如何。

标准效果

其中有一些粒子是不带重力的,比如MyDustId.OrangeShortFx

甚至还有一些是受重力以后行为变得怪异的

至于怎么利用好这些不同的Dust,就需要你们自己来设计了。除此之外,自定义Dust预计在第三部分会讲到,敬请期待。

此外我还会教你们一个去除dust重力的方法,那就是

Dust dust = Dust.NewDustDirect(hitbox.TopLeft(), hitbox.Width, hitbox.Height,
    MyDustId.OrangeFx3, 0, 0, 100, Color.White, 2f);
dust.noGravity = true;

直接设置刚刚生成的Dust的noGravity属性,true就是无重力。除此之外Dust也有很多属性,就像Player(玩家)那样,比如positionvelocity。大家可以自行探索。

Dust的生成可以放置在很多函数里面,无论这个重写函数是触发的还是持续的,比如我们可以写Shoot里,但是瞬发函数只能发出一个粒子,不好看出效果,所以我们需要让它多发射几个粒子:

public override bool Shoot(Player player, ref Vector2 position, ref float speedX, ref float speedY, ref int type, ref int damage, ref float knockBack) {
    // 发射100个粒子
    for (int i = 0; i < 100; i++) {
        Dust dust = Dust.NewDustDirect(player.position, player.width, player.height,
            MyDustId.Fire, speedX, speedY, 100, Color.White, 2f);
    }
    return false;
}

更多重写函数(钩子)

物品重写函数

武器持握

有些武器使用时是这样的,看起来没啥问题,其实手握的地方是枪托。而这显然不符合我们注重细节的要求,那么如何修改武器的持握位置呢?

这时候我们要使用物品的HoldoutOffset重写函数了

public override Vector2? HoldoutOffset() {
    return base.HoldoutOffset();
}

这个函数允许你设置useStyle为5且不是法杖的物品的使用贴图偏移量,如果函数返回null就是使用原版的偏移量,上图的武器我用了这个偏移量

public override Vector2? HoldoutOffset() {
    // X坐标往里移动10像素,Y坐标向上移动5像素
    return new Vector2(-10, -5);
}

于是效果是不是好多了?

除此之外,这两个函数也能修改物品的握持位置:

public override Vector2? HoldoutOrigin() {
    return base.HoldoutOrigin();
}
public override void HoldStyle(Player player) {
    base.HoldStyle(player);
}

值得一提的是HoldoutOrigin只对useStyle为5且是法杖的物品有用能修改物品贴图旋转中心的偏移量。HoldStyle 是物品在使用中(握在手上)会发生的事情,可以用来修改物品贴图的位置和旋转。

这个函数也十分有意思,能修改玩家在用这个武器(被选中)的时候玩家的动作。返回true如果你修改了玩家的动画。

public override bool HoldItemFrame(Player player) {
    return base.HoldItemFrame(player);
}

我们可以这么写

public override bool HoldItemFrame(Player player) {
    // 选中武器的时候设置为第3帧
    player.bodyFrame.Y = player.bodyFrame.Height * 2;
    return true;
}

这样只要切到这个武器玩家就会举起手来(笑)

弹药消耗

我们需要用到的函数是ConsumeAmmo

public override bool ConsumeAmmo(Player player) {
    return base.ConsumeAmmo(player);
}

如果这个函数返回true,那么就会消耗弹药,反之不消耗。这个函数每次消耗弹药都会执行,假设我们想要60%的几率不消耗弹药,我们需要让这个函数有60%的几率返回false

这里我们就要用到随机数Random类,正好泰拉瑞亚原版的Main.rand就是一个已经实例化好的随机类,我们直接调用Main.rand.Next()就好了。

public override bool ConsumeAmmo(Player player){
    // 为了有60的几率返回false就是40%几率返回true
    return Main.rand.Next(10) < 4;
}

Main.rand.Next(10)的意思就是在[0,10)的范围内随机选取一个整数。也就是0-9之间的一个整数,只要这个随机出来的数小于4,就是40%的几率了会是true了。因为,0,1,2,3,4,5,6,7,8,9,只有被标黑的会被判定为true,刚好是40%。

发射函数

Shoot这个函数我们之前提到过,但是没怎么仔细讲解,其实它就是允许修改武器射出弹幕的函数。同时它的参数包含射出弹幕的信息

public override bool Shoot(Player player, ref Vector2 position, ref float speedX, ref float speedY, ref int type, ref int damage, ref float knockBack)

我们可以通过type这个参数直接修改射出弹幕的类型,比如我可以这么写

public override bool Shoot(Player player, ref Vector2 position, ref float speedX, ref float speedY,
    ref int type, ref int damage, ref float knockBack) {
    type = Main.rand.Next(Main.projectileTexture.Length);
    return true;
}
我也不知道它射出了什么

这样就会把所有弹幕都随机射出来,包括Mod的弹幕。 当然,使用的时候要注意,把伤害调小点,不然你死都不知道怎么死的。

还有就是,像原版的三连发武器,我们不需要改Shoot,而是把item.useTime设为item.useAnimation的三分之一即可。这样一个动画时间内会使用这个物品三次,使用多次也是同理。此外,如果想让一轮射击之间有间隔,我们要设置item.reuseDelay,跟前两个属性一样,也是以帧为单位。

攻击敌人后特效

我们需要用到的重写函数是OnHitNPC,这个重写函数在近战武器砍到敌人触发。还有一个函数是ModifyHitNPC,这两个函数的区别是ModifyHitNPC发生在近战武器砍到敌人后,触发伤害前,你可以用ModifyHitNPC修改即将作用到敌人的伤害,击退,暴击等。而OnHitNPC是不能修改这些属性的。

我们想让近战砍到敌人以后给它加一个debuff,就要用到这个函数。我们掏出之前那个debuff的代码,这次我们要用到作用于NPC的那个Update了。

public override void Update(NPC npc, ref int buffIndex) {
    if (npc.lifeRegen > 0) {
         npc.lifeRegen = 0;
    }
    npc.lifeRegen -= 50;
}

注意,在Buff的Update里写减血速率通常不是好主意,我们以后会介绍更正规的写法,但是就目前来说,这么写就够了。

接下来我们让剑攻击到怪物以后给怪物加上这个Buff:

public override void OnHitNPC(Player player, NPC target, int damage, float knockBack, bool crit) {
    // 给怪物加上我们之前做的Buff,持续10秒
    target.AddBuff(ModContent.BuffType<SuperToxic>(), 600);
}

练习

基础

  1. 制作一个饰品,使得玩家速度达到一定值以后释放粒子特效答案
    首先我们要把这个效果写在饰品的Update里面
    // 如果玩家的速度大小超过5
    if (player.velocity.Length() > 5) {
        Dust dust = Dust.NewDustDirect(player.position, player.width, player.height,
                                       MyDustId.Fire, -player.velocity.X,
                                       -player.velocity.Y, 100, Color.White, 2.0f);
        // 粒子不受重力影响
        dust.noGravity = true;
    }
  2. 由于持续函数一秒钟会执行60次,有时候导致dust过多,那么怎么解决这个问题?答案
    我们可以让粒子特效每隔一帧发射一次,比如我们利用Main.time的奇偶性
    if (Main.time % 2 < 1) {
        Dust dust = Dust.NewDustDirect(player.position, player.width, player.height,
            MyDustId.Fire, -player.velocity.X, -player.velocity.Y, 100, Color.White, 2.0f);
        dust.noGravity = true;
    }
  3. 给Buff加个粒子效果吧
  4. 让武器的攻击有几率(而不是一定)将buff施加给怪物,让武器有几率射出弹幕
  5. 让武器射出随机粒子?

进阶

  1. NPC有哪些属性,如果buff修改npc的这些属性会发生什么?
  2. 随机数生成类除了Next还有什么,它们都会生成什么样的数据?
  3. 是否有关注https://github.com/CXUtk/TemplateMod2这个模板Mod的Github存储库?以后将会有更多的教程源码放在上面!
  4. 在学习第一部分的时候,都遇到过哪些让你摸不着头脑的错误?还有哪些你觉得应该在第一部分补充的内容?

《给物品加特技》有12个想法

  1. 给Buff加粒子效果:
    Dust.NewDustDirect(player.position, player.width, player.height, DustID.Fire, 0, 0, 100, Color.White, 2f);
    这个写到Update里
    有几率发射弹幕:
    if (Main.rand.Next(100) < 33)
    {
    return true;
    }
    写到Shoot里
    有几率施加Buff:
    if (Main.rand.Next(100) < 33)
    {
    target.AddBuff(BuffID, 1200);
    }
    写到OnHitNPC里
    随机粒子:
    Dust.NewDustDirect(player.position, player.width, player.height, Main.rand.Next(Main.dust.Length), speedX, speedY, 100, Color.White, 2f);

发表回复