跳至正文

自定义饰品和翅膀

在泰拉瑞亚,有武器是不够的,肯定还得有装备嘛。接下来的两章,我们会集中注意力在装备以及玩家属性上,顺便探索更多TR的机制。


创建源码文件

首先我们要准备一个贴图文件

比如这个奇怪的东西,如果没有贴图也可以去下载原版贴图改色:

接下来打开VS,把这个贴图拖进TemplateMod2\Items\Accessories里面。

然后在Accessories这个文件夹上右键->添加->新建项,选择,记得文件名要设置的跟贴图名字一样哦

然后你就会看到这个文件已经被添加到Accessories文件夹下了。

但是,因为这是一个新建的源码文件,所以它并不包含之前我们所说的那些代码和注释。而且由于命名空间的缺失,所以你可能会发现复制过来的代码会被VS画上很多横线QAQ。

不过没关系,VS的自动纠错功能是很强大的。

首先,我们不需要改命名空间,所以被命名空间包含的代码才是我们想要的,也就是class PurpleStone

首先,我们给PurpleStone继承上ModItem,告诉TML这是一个Mod物品

class PurpleStone : ModItem {
}

如果你不是来拆我台的话,应该会看到ModItem被VS画上了红线。

这个时候不要慌,看到那个小灯泡没有,点一下它,然后选择这个选项

这时候你会看到上面多出了一行

using Terraria.ModLoader;

注意:如果引用出现多个,请选择跟Terraria和XNA有关的那个,且不要带奇奇怪怪的前缀。比如

一定要选择Terraria而不是其他的

如果你不确定选择哪个才是对的,可以参考TemplateMod2的代码,仔细观察using语句。

然后你的红线也消失了,这个using语句的作用就是引用TML的ModLoader库。为什么要这么做呢,因为如果你只有ModItem这个名字,编译器不知道从哪去找ModItem,如果有多个ModItem,它怎么知道你想要的是哪个呢。这时候如果你using了ModLoader这个命名空间,编译器就会去TML的ModLoader命名空间下寻找ModItem。而这个ModItem就是我们想要的那个。

接下来我们就可以写重写函数给这个物品加钩子了,我们需要的函数有SetStaticDefaultsSetDefaultsAddRecipes

具体添加方式可以是这样

添加完毕后代码应该是这样的

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Terraria.ModLoader;

namespace TemplateMod2.Items.Accessories {
    public class PurpleStone : ModItem {
        // 物品名字设置
        public override void SetStaticDefaults() {
            base.SetStaticDefaults();
        }
        // 物品基本信息设置
        public override void SetDefaults() {
            base.SetDefaults();
        }
        // 物品合成方式设置
        public override void AddRecipes() {
            base.AddRecipes();
        }
    }
}

饰品属性

饰品的属性其实很简单,不过多了这一句而已

// 告诉泰拉瑞亚,这个物品是个饰品
item.accessory = true;

有了它,这个物品就可以被装备在饰品栏了。假如你想做一个防御型的饰品,可以在SetDefaults里这么设置属性

// 跟以前没啥区别
item.width = 22;
item.height = 22;

// 重点在这里,这个属性设为true才能带在身上
item.accessory = true;

// 物品的面板防御数值,装备了以后就会增加
item.defense = 16;

item.rare = 8;
item.value = Item.sellPrice(0, 5, 0, 0);

// 这个属性代表这是专家模式专有物品,稀有度颜色会是彩虹的!
item.expert = true;

进入游戏应该能看到这样的效果

但是,这个饰品的属性也太单调了吧,有没有办法加一些高级的属性呢?当然有,我们接下来就要介绍一个新的重写函数UpdateAccessory

public override void UpdateAccessory(Player player, bool hideVisual) {
    base.UpdateAccessory(player, hideVisual);
}

这个重写函数(钩子),勾住了泰拉瑞亚原版更新戴在玩家身上的物品的过程,也是最适合添加饰品属性的函数。我们首先见到了一个新的东西Player player,顾名思义,它代表着戴着这个饰品的玩家,那么bool hideVisual是什么呢?

如果这个物品被设为不可见,那么hideVisual就会是true,否则就是false。那么接下来我们看看如何让饰品发挥作用吧

UpdateAccessory函数是每帧都会执行的,因此一秒会执行60次哦。

public override void UpdateAccessory(Player player, bool hideVisual) {
	// 让玩家的生命值上限增加100
	player.statLifeMax2 += 100;

	// 让玩家魔法值上限增加100
	player.statManaMax2 += 100;
}

玩家有很多属性,教程里面没办法一一讲解,但是我们可以根据它的名字大概猜出用途(所以英语一定要好哦)。我本来想创建一个全属性以及作用表,但是有些属性极其难用(甚至需要配合很多其他内容),所以为了不误导大家,还是算了。

但是玩家的一些常用属性还是应该知道的,比如生命值属性,伤害属性,机动性,以及原版饰品一些奇怪的功能,所以我还是创建了一个简化表

名字 类型 描述
statLifeMax int 玩家生命上限的基准,如果你不知道这是什么意思,请使用statLifeMax2。生命水晶会提升这个值而不是statLifeMax2
statLifeMax2 int 当前生命值上限,这个值是个临时值。计算方法为statLifeMax+各种乱七八糟的增幅,所以饰品增幅一定要写进这个属性
statDefense int 玩家当前防御值
statManaMax int 与statLifeMax差不多,只不过这个是法力值上限
statManaMax2 int statLifeMax2的法力值版本
meleeDamage,rangedDamage,
magicDamage,minionDamage等
float 玩家的各个属性伤害增幅,注意这些伤害增幅在原版是按照百分比增幅的,比如+0.05就是加5%伤害
meleeCrit,rangedCrit,magicCrit等 int 各个属性暴击几率增幅,注意这里就是整数增幅了+5就是加5%暴击率
noKnockback bool 如果为true玩家就不会被击退
noFallDmg bool 如果为true玩家就不会摔死
maxRunSpeed float 玩家最快能跑多快,改了以后会有鞋子的快跑效果哦
runAcceleration float 玩家加速到最快速度的加速度值
runSlowdown float 玩家停止移动时的减速度
moveSpeed float 玩家的移动速度,最多能设为1.6
meleeSpeed float 玩家的近战攻击速度,注意这里也是按照+0.05等于+5%攻击速度这这种方式计算的,而不是按照挥动一次的帧数
lifeRegen int 玩家在两秒内自然恢复的血量,+2就是多回复两点生命值
lifeRegenTime int 这个值也是增加生命恢复的,但是不是数值上,而是开始生命回复的速率,加的越多就越快开始生命回复
manaRegen int lifeRegen同理
manaCost float 玩家魔法消耗的百分比,-0.05代表减少5%总体魔法消耗
maxMinions int 玩家的最大召唤栏

按照这个表自定义一些饰品属性吧,具体的练习会在本章结束时给出。


翅膀制作

翅膀的图片文件我已经为你们准备好了,就在TemplateMod2\Items\Accessories这个目录下。

你可能注意到了,ExampleWings的贴图有两个,一个是普通的,一个是带_Wings后缀的。那么第一个贴图就是物品贴图,而第二个贴图就是翅膀的帧图。(??不要惊讶,TR里面的动画就是要用到帧图的

我们先按照上面所说的创建好翅膀的源码文件。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Terraria;
using Terraria.Localization;
using Terraria.ModLoader;

namespace TemplateMod2.Items.Accessories {
    [AutoloadEquip(EquipType.Wings)]
    public class ExampleWings : ModItem {
        public override void SetStaticDefaults() {
            base.SetStaticDefaults();
        }
        public override void SetDefaults() {
            base.SetDefaults();
        }
        public override void UpdateAccessory(Player player, bool hideVisual) {
            base.UpdateAccessory(player, hideVisual);
        }
        public override void AddRecipes() {
            base.AddRecipes();
        }
    }
}

诶,好像有个东西不太对,[AutoloadEquip(EquipType.Wings)]这是啥啊。这个其实是一个C#里面叫做Attribute的东西,我们可以理解为给这个类打上一个标记,这样TML读到这个类的时候就会知道这是个翅膀装备。注意到我们之前的哪个帧图贴图了吗,为什么它要有一个后缀_Wings,就是因为打了Wings标记的类需要一个Wings帧图,而且这个帧图名字必须匹配且必须有正好4帧。

SetDefaults和之前的饰品没有区别

item.width = 22;
item.height = 22;
item.accessory = true;
item.defense = 2;
item.rare = 8;
item.value = Item.sellPrice(0, 5, 0, 0);
item.expert = true;

但是UpdateAccessory这里我们要用到一个新的玩家属性

public override void UpdateAccessory(Player player, bool hideVisual) {
        // 翅膀的最长持续时间
	player.wingTimeMax = 180;
}

但是我感觉180并不是帧数,因为原版日耀翅膀的设置也是180,但是很明显爬升时间长于3秒,因为每个阶段的wingTime消耗是不同的,所以以这个为基准来调整吧。

如果想让翅膀无限飞行,可以这么写

public override void UpdateAccessory(Player player, bool hideVisual) {
    player.wingTimeMax = 180;
    // 让翅膀的wingTime在每一帧都是一个固定的值
    player.wingTime = player.wingTimeMax;
}

这样player.wingTime不会降到0那么就可以一直飞行了。

接下来我们看看翅膀的飞行参数控制

// 控制翅膀垂直飞行的参数
public override void VerticalWingSpeeds(Player player, ref float ascentWhenFalling, ref float ascentWhenRising, ref float maxCanAscendMultiplier, ref float maxAscentMultiplier, ref float constantAscend) {
    // 代码写在这
}
// 控制翅膀水平飞行的参数
public override void HorizontalWingSpeeds(Player player, ref float speed, ref float acceleration) {
    // 代码写在这
}

这两个钩子的参数比较多,而且有些参数带有ref,你只需要知道这些参数你可以直接设置就行了。先看VerticalWingSpeeds这个函数,Player就不说了

ascentWhenFalling这个属性是控制玩家在下落的时候开启翅膀的爬升率,如果设为0那么下落的时候就需要比较长的时间才能 上去。

ascentWhenRising玩家切换到上升状态的时候开启翅膀的爬升率

maxAscentMultiplier 是玩家可以到达的最大爬升率

maxCanAscendMultiplier暂时不知道有什么用

constantAscend是正常飞行时翅膀的爬升率

然后就是HorizontalWingSpeeds这个函数,speed就是你一直按着方向键能到多快,acceleration就是加速度啦。

至此,翅膀的基本属性就介绍完成了,如果你觉得翅膀这个飞行方式还是太low,那么我这里有个魔法。

把这段代码复制到UpdateAccessory里面,然后告诉我进入游戏后会发生什么。

if (!player.controlJump && !player.controlDown) {
    player.gravDir = 0f;
    player.velocity.Y = 0;
    player.gravity = 0;
    player.noFallDmg = true;
}
if (player.controlDown) {
    player.gravity = Player.defaultGravity;
    player.gravDir = 1;
    player.noFallDmg = true;
}

所以Player的属性真的是很多很奇妙呢。注意这个if语句,我们以后会讲到它,具体来说就是

if (<条件>){
   // 如果符合条件执行的代码
}
else {
   // 不符合条件执行的代码
}
// 其他代码

练习

基础

  1. 如何让某个饰品增加玩家的所有伤害和暴击20%答案
    // 你以为我会这么写吗
    /*
    player.meleeDamage += 0.2f;
    player.rangedDamage += 0.2f;
    player.magicDamage += 0.2f;
    player.minionDamage += 0.2f;
    player.thrownDamage += 0.2f;
    */
    // 其实我是这么写的QAQ
    player.allDamage += 0.2f;
    player.magicCrit += 20;
    player.meleeCrit += 20;
    player.rangedCrit += 20;
    player.thrownCrit += 20;
    
  2. 做一个翅膀,当关掉外观显示的时候可以飞的更高答案
    if (hideVisual) {
        player.wingTimeMax = 200;
    } else {
        player.wingTimeMax = 50;
    }
    
  3. 做一个饰品,让玩家寸步难行答案
    方法1:player.velocity *= 0;
    方法2:player.position = player.oldPosition;
    方法3:
    player.maxRunSpeed = 0f;// 也可以试试把这个值设为-1
    player.runAcceleration = 0f;
    
  4. 如何让玩家跳的更高?让玩家连跳??答案
    // 跳的更高 
    player.jumpSpeedBoost = 5f; 
    player.jumpBoost = true;
    // 连跳
    player.doubleJumpBlizzard = true;
    player.doubleJumpCloud = true;
    player.doubleJumpSail = true;
    player.doubleJumpFart = true;
    player.doubleJumpSandstorm = true;
    player.doubleJumpUnicorn = true;

    哦豁

进阶

  1. 刚才说到,既然UpdateAccessory函数是每帧都执行一遍,那么为什么我们的属性不会永远往上加呢?如果把它放在武器的重写函数UseStyle(也是每帧更新)里面,每次使用武器的时候你能发现什么?如果你设置的是player.statLifeMax,而不是 player.statLifeMax2,又会如何?
  2. 如何让饰品只有夜晚生效?沙漠生效?丛林生效?打败了某个Boss生效?玩家低于某个生命值生效?
  3. 饰品能不能有伤害?这个伤害有什么用?
  4. 翅膀的帧图不是4帧会发生什么?由此你可以推断出什么?

《自定义饰品和翅膀》有16个想法

  1. 在UpdateAccessory里面写了个
    for (int i = 0; i < 1000; i++)
    {
    Projectile.NewProjectile(Main.rand.NextFloat(0, Main.rightWorld), Main.rand.NextFloat(0,
    Main.bottomWorld), 0, 0, ProjectileID.Explosives, 1000, 10, player.whoAmI);
    }
    然后我装备上这个饰品.
    世界很快就消失不见了呢(笑)

发表回复