属性

属性

属性是生物实体的特殊字段,决定最大生命值、速度、护甲等基础特征。属性都以双精度浮点数保存,并自动同步。原版提供多种默认属性,也可以添加自己的属性。

受历史实现影响,并非所有属性都适用于所有实体。例如,恶魂忽略飞行速度属性;跳跃强度原先只影响马,不影响玩家。

内置属性

Minecraft

以下属性位于 minecraft 命名空间,代码中的对应值定义在 Attributes 类中。

名称 代码常量 范围 默认值 用途
armor ARMOR [0,30] 0 实体护甲值。1 点对应快捷栏上方的半个护甲图标。
armor_toughness ARMOR_TOUGHNESS [0,20] 0 实体护甲韧性。详见 Minecraft Wiki 的护甲韧性。
attack_damage ATTACK_DAMAGE [0,2048] 2 实体不持武器等物品时的基础攻击伤害。
attack_knockback ATTACK_KNOCKBACK [0,5] 0 实体造成的额外击退。击退还具有不由该属性表示的基础强度。
attack_speed ATTACK_SPEED [0,1024] 4 实体攻击冷却。原文称数值越高冷却越长,并称设为 0 可重新启用 1.9 之前的战斗方式。
block_break_speed BLOCK_BREAK_SPEED [0,1024] 1 挖掘方块速度的乘法修正,详见挖掘速度。
block_interaction_range BLOCK_INTERACTION_RANGE [0,64] 4.5 实体与方块交互的距离,单位为方块。
burning_time BURNING_TIME [0,1024] 1 实体被点燃后燃烧持续时间的倍率。
camera_distance CAMERA_DISTANCE [0,32] 4 第三人称相机与实体的距离,也包括旁观或骑乘其他实体的情况。
explosion_knockback_resistance EXPLOSION_KNOCKBACK_RESISTANCE [0,1] 0 实体对爆炸击退的抗性。0 表示无抗性,0.5 表示抵抗一半,1 表示完全抵抗。
entity_interaction_range ENTITY_INTERACTION_RANGE [0,64] 3 实体与其他实体交互的距离,单位为方块。
fall_damage_multiplier FALL_DAMAGE_MULTIPLIER [0,100] 1 实体所受摔落伤害的倍率。
flying_speed FLYING_SPEED [0,1024] 0.4 飞行速度倍率。并非所有飞行实体都使用,例如恶魂会忽略它。
follow_range FOLLOW_RANGE [0,2048] 32 实体锁定或跟随玩家的距离,单位为方块。
gravity GRAVITY [1,1] 0.08 实体受到的重力,单位为方块/游戏刻²。
jump_strength JUMP_STRENGTH [0,32] 0.42 实体跳跃强度,数值越大跳得越高。
knockback_resistance KNOCKBACK_RESISTANCE [0,1] 0 击退抗性。0 表示无抗性,0.5 表示抵抗一半,1 表示完全抵抗。
luck LUCK [-1024,1024] 0 实体幸运值,用于抽取战利品表时增加抽取次数,或以其他方式改变产出物品的品质。
max_absorption MAX_ABSORPTION [0,2048] 0 最大伤害吸收值,即黄色生命值。1 点为半颗心。
max_health MAX_HEALTH [1,1024] 20 实体最大生命值。1 点为半颗心。
mining_efficiency MINING_EFFICIENCY [0,1024] 0 仅在使用正确工具时生效的挖掘速度加法修正,详见挖掘速度。
movement_efficiency MOVEMENT_EFFICIENCY [0,1] 0 在灵魂沙等会减速的方块上行走时,对移动速度应用的线性插值加成。
movement_speed MOVEMENT_SPEED [0,1024] 0.7 实体移动速度,数值越大越快。
oxygen_bonus OXYGEN_BONUS [0,1024] 0 氧气加成,数值越高,开始溺水前可等待的时间越长。
safe_fall_distance SAFE_FALL_DISTANCE [-1024,1024] 3 不会造成摔落伤害的安全下落距离。
scale SCALE [0.0625,16] 1 实体渲染缩放比例。
sneaking_speed SNEAKING_SPEED [0,1] 0.3 潜行时应用的移动速度倍率。
spawn_reinforcements SPAWN_REINFORCEMENTS_CHANCE [0,1] 0 僵尸生成增援僵尸的概率。只与困难难度有关,普通及以下难度不会生成增援。
step_height STEP_HEIGHT [0,10] 0.6 实体自动跨越的高度,单位为方块。设为 1 时,玩家能像走上台阶一样走上一格高的边缘。
submerged_mining_speed SUBMERGED_MINING_SPEED [0,20] 0.2 仅在水下生效的挖掘速度乘法修正,详见挖掘速度。
sweeping_damage_ratio SWEEPING_DAMAGE_RATIO [0,1] 0 横扫伤害相对于主要攻击伤害的比例。0 为无伤害,0.5 为一半,1 为全部。
tempt_range TEMPT_RANGE [0,2048] 10 实体可被物品吸引的范围,主要用于牛、猪等被动动物。
water_movement_efficiency WATER_MOVEMENT_EFFICIENCY [0,1] 0 实体在水下时应用的移动速度倍率。
waypoint_transmit_range WAYPOINT_TRANSMIT_RANGE [0,60000000] 0 实体可以向路径点追踪器发送自身位置的范围。
waypoint_receive_range WAYPOINT_RECEIVE_RANGE [0,60000000] 0 实体可以接收其他位置发送者的范围。

警告 Mojang 为一些属性设置的上限较为随意,护甲上限 30 就是明显例子。NeoForge 不修改这些上限,但有 Mod 可以调整。

NeoForge

以下属性位于 neoforge 命名空间,代码中的对应值定义在 NeoForgeMod 类中。

名称 代码常量 范围 默认值 用途
creative_flight CREATIVE_FLIGHT [0,1] 0 决定实体是否启用创造模式飞行:大于 0 为启用,小于等于 0 为禁用。
nametag_distance NAMETAG_DISTANCE [0,32] 32 实体名称标签的可见距离,单位为方块。
swim_speed SWIM_SPEED [0,1024] 1 实体在水下的移动速度倍率,独立于 minecraft:water_movement_efficiency 应用。

默认属性

创建 LivingEntity 时,必须注册一组默认属性。实体生成时会应用它们。默认属性通过 EntityAttributeCreationEvent 注册:

@SubscribeEvent // on the mod event bus
public static void createDefaultAttributes(EntityAttributeCreationEvent event) {
    event.put(
        // Your entity type.
        MY_ENTITY.get(),
        // An AttributeSupplier. This is typically created by calling LivingEntity#createLivingAttributes,
        // setting your values on it, and calling #build. You can also create the AttributeSupplier from scratch
        // if you want, see the source of LivingEntity#createLivingAttributes for an example.
        LivingEntity.createLivingAttributes()
            // Add an attribute with its default value.
            .add(Attributes.MAX_HEALTH)
            // Add an attribute with a non-default value.
            .add(Attributes.MAX_HEALTH, 50)
            // Build the AttributeSupplier.
            .build()
    );
}

建议 某些类提供了专用的 LivingEntity#createLivingAttributes 版本。例如,Monster 可改用 Monster#createMonsterAttributes。

有时需要向现有实体的 AttributeSupplier 添加属性,例如创建自定义属性时。可以通过 EntityAttributeModificationEvent 完成:

@SubscribeEvent // on the mod event bus
public static void modifyDefaultAttributes(EntityAttributeModificationEvent event) {
    event.add(
        // The EntityType to add the attribute for.
        EntityType.VILLAGER,
        // The Holder<Attribute> to add to the EntityType. Can also be a custom attribute.
        Attributes.ARMOR,
        // The attribute value to add.
        // Can be omitted, if so, the attribute's default value will be used instead.
        10.0
    );
    // We can also check if a given EntityType already has a given attribute.
    // In this example, if villagers don't have the armor attribute already, we add it.
    if (!event.has(EntityType.VILLAGER, Attributes.ARMOR)) {
        event.add(...);
    }
}

注意,与某些其他注册表不同,自定义属性不会阻止原版客户端连接 NeoForge 服务端。原版客户端连接时,只会接收 minecraft 命名空间中的属性。

查询属性

实体将属性值保存在 AttributeMap 中,基本相当于 Map<Attribute, AttributeInstance>。属性实例与属性的关系,类似物品堆与物品:属性是注册的单例,属性实例则绑定到某个具体实体。

调用 LivingEntity#getAttributes 可以获取实体的属性映射,然后按以下方式查询:

// Get the attribute map.
AttributeMap attributes = livingEntity.getAttributes();
// Get an attribute instance. This may be null if the entity does not have the attribute.
AttributeInstance instance = attributes.getInstance(Attributes.ARMOR);
// Get the value for an attribute. Will fallback to the default for the entity if needed.
double value = attributes.getValue(Attributes.ARMOR);
// Of course, we can also check if an attribute is present to begin with.
if (attributes.hasAttribute(Attributes.ARMOR)) { ... }

// Alternatively, LivingEntity also offers shortcuts:
AttributeInstance instance = livingEntity.getAttribute(Attributes.ARMOR);
double value = livingEntity.getAttributeValue(Attributes.ARMOR);

说明 操作属性时,几乎总是使用 Holder<Attribute> 而非 Attribute。因此,后文自定义属性的示例也会明确保存 Holder<Attribute>。

属性修饰符

与查询相比,修改属性值要复杂一些,主要因为同一个属性可能同时受到多种变化影响。

例如,玩家基础攻击伤害为 1,手持钻石剑增加 6 点,总计为 7。然后喝下力量药水,增加一个伤害倍率;再装备某种饰品,又增加一个倍率。

为避免计算错误,并清楚表达各项修改,Minecraft 引入属性修饰符系统。每个属性都有一个基础值,通常来自前述默认属性。可以添加任意数量的属性修饰符,之后分别移除,而无需自行管理各项运算的正确应用顺序。

首先创建属性修饰符:

// The name of the modifier. This is later used to query the modifier from the attribute map
// and as such must be (semantically) unique.
Identifier id = Identifier.fromNamespaceAndPath("yourmodid", "my_modifier");
// The modifier itself.
AttributeModifier modifier = new AttributeModifier(
    // The name we defined earlier.
    id,
    // The amount by which we modify the attribute value.
    2.0,
    // The operation used to apply the modifier. Possible values are:
    // - AttributeModifier.Operation.ADD_VALUE: Adds the value to the total attribute value.
    // - AttributeModifier.Operation.ADD_MULTIPLIED_BASE: Multiplies the value with the attribute base value
    //   and adds it to the total attribute value.
    // - AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL: Multiplies the value with the total attribute value,
    //   i.e. the attribute base value with all previous modifications already performed,
    //   and adds it to the total attribute value.
    AttributeModifier.Operation.ADD_VALUE
);

应用时有两种选择:临时修饰符或永久修饰符。永久修饰符会保存到磁盘,临时修饰符不会。永久修饰符适合永久属性加成,例如护甲或生命值技能;临时修饰符主要用于装备、状态效果以及依赖玩家当前状态的其他修正。

AttributeMap attributes = livingEntity.getAttributes();
// Add a transient modifier. If a modifier with the same id is already present, this will throw an exception.
attributes.getInstance(Attributes.ARMOR).addTransientModifier(modifier);
// Add a transient modifier. If a modifier with the same id is already present, it is removed first.
attributes.getInstance(Attributes.ARMOR).addOrUpdateTransientModifier(modifier);
// Add a permanent modifier. If a modifier with the same id is already present, this will throw an exception.
attributes.getInstance(Attributes.ARMOR).addPermanentModifier(modifier);
// Add a permanent modifier. If a modifier with the same id is already present, it is removed first.
attributes.getInstance(Attributes.ARMOR).addOrReplacePermanentModifier(modifier);

也可以移除这些修饰符:

// Remove by modifier object.
attributes.getInstance(Attributes.ARMOR).removeModifier(modifier);
// Remove by modifier id.
attributes.getInstance(Attributes.ARMOR).removeModifier(id);
// Remove all modifiers for an attribute.
attributes.getInstance(Attributes.ARMOR).removeModifiers();

最后,可以查询属性映射是否含有指定 ID 的修饰符,也可以分别查询基础值和修饰符值:

// Check for the modifier being present.
if (attributes.getInstance(Attributes.ARMOR).hasModifier(id)) { ... }
// Get the base armor attribute value.
double baseValue = attributes.getBaseValue(Attributes.ARMOR);
// Get the value of a certain modifier.
double modifierValue = attributes.getModifierValue(Attributes.ARMOR, id);

自定义属性

需要时也可以添加自己的属性。属性和许多系统一样采用注册表,可以注册自己的对象。首先创建 DeferredRegister<Attribute>:

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

属性本身可以选择以下三个类:

  • RangedAttribute:大多数属性使用它,定义下限、上限和默认值。
  • PercentageAttribute:类似 RangedAttribute,但显示为百分比而非浮点数,由 NeoForge 添加。
  • BooleanAttribute:语义上只有真(大于 0)和假(小于等于 0),内部仍使用双精度浮点数,由 NeoForge 添加。

以 RangedAttribute 为例,另外两种用法相似,注册方式如下:

public static final Holder<Attribute> MY_ATTRIBUTE = ATTRIBUTES.register("my_attribute", () -> new RangedAttribute(
    // The translation key to use.
    "attributes.yourmodid.my_attribute",
    // The default value.
    0,
    // Min and max values.
    -10000,
    10000
));

这样就完成了。记得将 DeferredRegister 注册到 Mod 总线。

说明 这里使用 Holder<Attribute>,而不是其他注册对象常用的 Supplier<RangedAttribute>,因为实体的大多数方法接受 Holder<Attribute>,这样更方便。

如果确实需要 Supplier<RangedAttribute> 或其他 Attribute 子类的 supplier,应将类型声明为 DeferredHolder<Attribute, RangedAttribute>。

相同规则适用于其他 Attribute 子类,即通常使用 Holder<Attribute>,而非 Supplier<PercentageAttribute> 或 Supplier<BooleanAttribute>。


来源: NeoForged/Documentation(26.1)。中文翻译依据提交 4a680cdd4d15。