Skip to main content
Version: 1.21.4

属性(Attributes)

属性(Attributes)是 生物实体 的特殊字段,用于决定如最大生命值、速度或护甲等基础属性。所有属性都以 double 类型的数值存储,并会自动同步。Minecraft 原版(Vanilla)提供了丰富的默认属性,你也可以添加自定义属性。

由于历史实现方式的原因,并非所有属性都适用于所有实体。例如,飞行速度属性不会影响恶魂(ghast),而跳跃力只对马有作用,对玩家无效。

内置属性(Built-In Attributes)

Minecraft

以下属性属于 minecraft 命名空间,其代码中的取值可在 Attributes 类中找到。

名称代码中的名称范围默认值用途
armorARMOR[0,30]0实体的护甲值。数值为 1 时,代表在快捷栏上方显示半个胸甲图标。
armor_toughnessARMOR_TOUGHNESS[0,20]0实体的护甲韧性值。更多信息参见 护甲韧性(Armor Toughness)在 Minecraft Wiki 上的相关内容。
attack_damageATTACK_DAMAGE[0,2048]2实体造成的基础攻击伤害,不包含任何武器或类似物品的加成。
attack_knockbackATTACK_KNOCKBACK[0,5]0实体造成的额外击退效果。击退还包含一个基础强度,该属性未涵盖此部分。
attack_speedATTACK_SPEED[0,1024]4实体的攻击冷却时间。数值越高,冷却越长。将其设为 0 实际上会恢复 1.9 版本前的战斗机制。
block_break_speedBLOCK_BREAK_SPEED[0,1024]1实体挖掘方块的速度(乘法修饰器)。更多信息参见 挖掘速度(Mining Speed)。
block_interaction_rangeBLOCK_INTERACTION_RANGE[0,64]4.5实体与方块交互的有效距离(单位为方块数)。
burning_timeBURNING_TIME[0,1024]1实体被点燃时燃烧持续时间的倍率。
explosion_knockback_resistanceEXPLOSION_KNOCKBACK_RESISTANCE[0,1]0实体对爆炸击退的抗性。该值为百分比,例如 0 表示无抗性,0.5 表示一半抗性,1 表示完全抗性。
entity_interaction_rangeENTITY_INTERACTION_RANGE[0,64]3实体与其他实体交互的有效距离(单位为方块数)。
fall_damage_multiplierFALL_DAMAGE_MULTIPLIER[0,100]1实体所受摔落伤害的倍率。
flying_speedFLYING_SPEED[0,1024]0.4飞行速度的倍率。并非所有飞行实体都会用到该属性,例如恶魂(ghast)会忽略它。
follow_rangeFOLLOW_RANGE[0,2048]32实体追踪/跟随玩家的最大距离(单位为方块数)。
gravityGRAVITY[1,1]0.08作用于实体的重力值,单位为每 tick 的方块数的平方。
jump_strengthJUMP_STRENGTH[0,32]0.42实体的跳跃强度。数值越高,跳得越高。
knockback_resistanceKNOCKBACK_RESISTANCE[0,1]0实体的击退抗性。该值为百分比,例如 0 表示无抗性,0.5 表示一半抗性,1 表示完全抗性。
luckLUCK[-1024,1024]0实体的幸运值。该值用于 战利品表(loot tables)抽奖时,影响额外掉落或物品品质的修正。
max_absorptionMAX_ABSORPTION[0,2048]0实体的最大吸收值(黄色心),数值为 1 表示半格心。
max_healthMAX_HEALTH[1,1024]20实体的最大生命值,数值为 1 表示半格心。
mining_efficiencyMINING_EFFICIENCY[0,1024]0实体挖掘方块的速度(加法修饰器),仅当使用正确工具时生效。更多信息参见 挖掘速度(Mining Speed)。
movement_efficiencyMOVEMENT_EFFICIENCY[0,1]0实体在如灵魂沙等减速方块上行走时,线性插值的移动速度加成。
movement_speedMOVEMENT_SPEED[0,1024]0.7实体的移动速度,数值越高移动越快。
oxygen_bonusOXYGEN_BONUS[0,1024]0实体的额外氧气值。数值越高,实体开始溺水所需时间越长。
safe_fall_distanceSAFE_FALL_DISTANCE[-1024,1024]3实体的安全摔落距离,即在此距离范围内不会受到摔落伤害。
scaleSCALE[0.0625,16]1实体的渲染缩放比例。
sneaking_speedSNEAKING_SPEED[0,1]0.3实体潜行时应用的移动速度倍率。
spawn_reinforcementsSPAWN_REINFORCEMENTS_CHANCE[0,1]0僵尸生成其他僵尸的概率。仅在困难(hard)难度下有效,普通或更低难度下不会生成援军。
step_heightSTEP_HEIGHT[0,10]0.6实体的台阶高度(单位为方块数)。值为 1 时,玩家可像走上半砖一样直接跨越 1 格高的台阶。
submerged_mining_speedSUBMERGED_MINING_SPEED[0,20]0.2实体在水下挖掘方块的速度(乘法修饰器)。更多信息参见 挖掘速度(Mining Speed)。
sweeping_damage_ratioSWEEPING_DAMAGE_RATIO[0,1]0横扫攻击造成的伤害比例,按主攻击伤害的百分比计算。0 表示不造成伤害,0.5 为一半伤害,1 为全部伤害。
tempt_rangeTEMPT_RANGE[0,2048]10实体可被物品诱导的范围。主要用于被动动物,如牛或猪。
water_movement_efficiencyWATER_MOVEMENT_EFFICIENCY[0,1]0实体在水下时应用的移动速度倍率。
warning

部分属性的上限是 Mojang 随意设定的,具有一定的随意性。最典型的是护甲值,其上限为 30。NeoForge 并不会修改这些上限,不过已经有模组可以更改它们。

NeoForge

以下属性属于 neoforge 命名空间,其在代码中的定义可在 NeoForgeMod 类中找到。

名称代码中的名称取值范围默认值用途
creative_flightCREATIVE_FLIGHT[0,1]0决定实体是否开启创造模式飞行(大于 0 时开启,0 或更小则关闭)。
nametag_distanceNAMETAG_DISTANCE[0,32]32实体的名称标签在多远距离内可见,单位为方块。
swim_speedSWIM_SPEED[0,1024]1实体在水下时的移动速度倍率。该属性的作用独立于 minecraft:water_movement_efficiency

默认属性(Default Attributes)

在创建一个 LivingEntity(生物实体)时,必须为其注册一组默认属性。当实体被 生成 时,会自动设置这些默认属性。默认属性的注册通常在 EntityAttributeCreationEvent 事件中完成,示例如下:

@SubscribeEvent
public static void createDefaultAttributes(EntityAttributeCreationEvent event) {
event.put(
// 你的实体类型。
MY_ENTITY.get(),
// AttributeSupplier(属性提供器)。通常通过调用 LivingEntity#createLivingAttributes 创建,
// 在其上设置你的属性值,然后调用 #build 方法。你也可以完全自定义 AttributeSupplier,
// 具体可参考 LivingEntity#createLivingAttributes 的源码示例。
LivingEntity.createLivingAttributes()
// 添加一个属性,并使用默认值。
.add(Attributes.MAX_HEALTH)
// 添加一个属性,并指定非默认值。
.add(Attributes.MAX_HEALTH, 50)
// 构建 AttributeSupplier。
.build()
);
}
tip

某些类拥有 LivingEntity#createLivingAttributes 的专用版本。例如,Monster 类提供了 Monster#createMonsterAttributes 方法,可直接使用。

在某些情况下,比如你需要为现有实体添加 自定义属性 时,需要向已有实体的 AttributeSupplier 添加属性。这可以通过 EntityAttributeModificationEvent 事件实现,方法如下:

@SubscribeEvent
public static void modifyDefaultAttributes(EntityAttributeModificationEvent event) {
event.add(
// 要为其添加属性的 EntityType。
EntityType.VILLAGER,
// 要添加到 EntityType 的 Holder<Attribute>,也可以是自定义属性。
Attributes.ARMOR,
// 要添加的属性值。
// 可以省略,如果省略则会使用属性的默认值。
10.0
);
// 我们也可以检查某个 EntityType 是否已经拥有指定属性。
// 例如,如果村民还没有护甲属性,则为其添加。
if (!event.has(EntityType.VILLAGER, Attributes.ARMOR)) {
event.add(...);
}
}

需要注意的是,与其他一些注册表(Registry)不同,自定义属性的存在并不会阻止原版客户端连接到 NeoForge 服务器。如果原版客户端连接,它只会接收到 minecraft 命名空间下的属性。

属性查询(Querying Attributes)

属性值会存储在实体的 AttributeMap 中,本质上这是一个 Map<Attribute, AttributeInstance>。属性实例(AttributeInstance)和属性(Attribute)的关系,类似于物品堆叠(item stacks)和物品(items)的关系:属性是注册的单例,而属性实例是绑定到具体实体上的具体属性对象。

可以通过调用 LivingEntity#getAttributes 获取实体的 AttributeMap。你可以像下面这样查询属性:

// 获取属性映射表。
AttributeMap attributes = livingEntity.getAttributes();
// 获取某个属性的实例。如果实体没有该属性,则可能为 null。
AttributeInstance instance = attributes.getInstance(Attributes.ARMOR);
// 获取某个属性的值。如果需要,会回退到实体的默认值。
double value = attributes.getValue(Attributes.ARMOR);
// 当然,我们也可以先检查属性是否存在。
if (attributes.hasAttribute(Attributes.ARMOR)) { ... }

// 另外,LivingEntity 还提供了简便方法:
AttributeInstance instance = livingEntity.getAttribute(Attributes.ARMOR);
double value = livingEntity.getAttributeValue(Attributes.ARMOR);
info

在处理属性时,几乎总是使用 Holder<Attribute> 而不是直接使用 Attribute。因此在自定义属性(见下文)时,我们会显式存储 Holder<Attribute>

属性修饰符(Attribute Modifiers)

与属性查询不同,修改属性值并不那么简单。这主要是因为同一个属性可能会同时受到多处修改的影响。

比如说:你是一个玩家,攻击伤害属性为 1。你手持钻石剑,额外提供 6 点攻击伤害,总共就是 7 点攻击伤害。然后你喝了一瓶力量药水,获得了伤害加成。你还装备了某种饰品,又叠加了另一个伤害倍增效果。 为了避免计算错误,并更好地表达属性值是如何被修改的,Minecraft 引入了属性修饰符系统(attribute modifier system)。在这个系统中,每个属性(attribute)都有一个基础值(base value),这个值通常来源于我们前面讨论过的默认属性。然后,我们可以添加任意数量的属性修饰符(attribute modifiers),而且这些修饰符可以单独移除,无需担心如何正确应用操作。

让我们开始,先创建一个属性修饰符:

// 修饰符的名称。之后可以通过该名称从属性映射中查询修饰符,
// 因此它必须在语义上保持唯一。
ResourceLocation id = ResourceLocation.fromNamespaceAndPath("yourmodid", "my_modifier");
// 创建修饰符本身。
AttributeModifier modifier = new AttributeModifier(
// 我们之前定义的名称。
id,
// 修改属性值的数值。
2.0,
// 应用修饰符时使用的操作。可选值如下:
// - AttributeModifier.Operation.ADD_VALUE: 将该数值直接加到属性总值上。
// - AttributeModifier.Operation.ADD_MULTIPLIED_BASE: 用该数值乘以属性基础值,
// 然后加到属性总值上。
// - AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL: 用该数值乘以属性总值
// (即基础值加上之前所有修饰的结果),再加到属性总值上。
AttributeModifier.Operation.ADD_VALUE
);

现在,要应用这个修饰符,有两种方式:可以将其添加为临时修饰符(transient modifier),或者作为永久修饰符(permanent modifier)。永久修饰符会被保存到磁盘,而临时修饰符不会。永久修饰符适用于诸如永久属性加成(比如某类护甲或生命技能)等场景,而临时修饰符主要用于 装备生物效果 以及其他依赖玩家当前状态的修饰。

AttributeMap attributes = livingEntity.getAttributes();
// 添加一个临时修饰符。如果已存在相同 id 的修饰符,将抛出异常。
attributes.getInstance(Attributes.ARMOR).addTransientModifier(modifier);
// 添加一个临时修饰符。如果已存在相同 id 的修饰符,会先移除再添加。
attributes.getInstance(Attributes.ARMOR).addOrUpdateTransientModifier(modifier);
// 添加一个永久修饰符。如果已存在相同 id 的修饰符,将抛出异常。
attributes.getInstance(Attributes.ARMOR).addPermanentModifier(modifier);
// 添加一个永久修饰符。如果已存在相同 id 的修饰符,会先移除再添加。
attributes.getInstance(Attributes.ARMOR).addOrReplacePermanentModifier(modifier);

这些修饰符同样也可以被移除:

// 通过修饰符对象移除。
attributes.getInstance(Attributes.ARMOR).removeModifier(modifier);
// 通过修饰符 id 移除。
attributes.getInstance(Attributes.ARMOR).removeModifier(id);
// 移除某个属性上的所有修饰符。
attributes.getInstance(Attributes.ARMOR).removeModifiers();

最后,我们还可以查询属性映射(attribute map)中是否存在某个特定 ID 的修饰符,也可以分别获取基础值和修饰符的数值,如下所示:

// 检查该修饰符是否存在。
if (attributes.getInstance(Attributes.ARMOR).hasModifier(id)) { ... }
// 获取基础护甲属性值。
double baseValue = attributes.getBaseValue(Attributes.ARMOR);
// 获取某个修饰符的数值。
double modifierValue = attributes.getModifierValue(Attributes.ARMOR, id);

自定义属性(Custom Attributes)

如有需要,你也可以添加自定义属性。和许多其他系统一样,属性本身也是一个 注册表(Registry),你可以向其中注册自己的对象。首先,创建一个 DeferredRegister<Attribute>,如下所示:

public static final DeferredRegister<Attribute> ATTRIBUTES = DeferredRegister.create(
BuiltInRegistries.ATTRIBUTE, "yourmodid");

对于属性本身,你可以从以下三个类中选择:

  • RangedAttribute:大多数属性都使用这个类,它定义了属性的上下界以及默认值。
  • PercentageAttribute:与 RangedAttribute 类似,但显示为百分比而不是浮点数。此类为 NeoForge 新增。
  • BooleanAttribute:只具有语义上的 true (> 0) and false (<= 0) 的属性。内部依然使用 double 类型。此类为 NeoForge 新增。

RangedAttribute 为例(另外两个类用法类似),注册一个属性的代码如下:

public static final Holder<Attribute> MY_ATTRIBUTE = ATTRIBUTES.register("my_attribute", () -> new RangedAttribute(
// 用于翻译的键。
"attributes.yourmodid.my_attribute",
// 默认值。
0,
// 最小值和最大值。
-10000,
10000
));

就是这样!只要别忘了把你的 DeferredRegister 注册到 mod bus 上,你就可以开始使用了。

info

这里我们使用 Holder<Attribute> 而不是像许多其他注册对象那样用 Supplier<RangedAttribute>,这样在与实体(entity)交互时会更加方便(因为大多数实体方法都期望 Holder<Attribute> 类型)。

如果你确实需要一个 Supplier<RangedAttribute>(或任何其他 Attribute 子类的 supplier),你应该使用 DeferredHolder<Attribute, RangedAttribute> 作为类型。

同样的规则也适用于其他 Attribute 子类,也就是说,我们通常使用 Holder<Attribute>,而不是 Supplier<PercentageAttribute>Supplier<BooleanAttribute>