生物实体、生物与玩家
生物实体是实体中的一大类,都继承自 LivingEntity。其中包括通过 Mob 子类实现的生物、通过 Player 子类实现的玩家,以及通过 ArmorStand 子类实现的盔甲架。
生物实体具有普通实体没有的额外特征,例如属性、状态效果、伤害追踪等。
生命值、伤害与治疗
另见:属性。
生物实体与其他实体最显著的区别之一是完整的生命值系统,通常具有最大生命值、当前生命值,有些还有护甲或自然恢复机制。
默认最大生命值由 minecraft:max_health 属性决定,生成时会将当前生命值设为相同数值。调用 Entity#hurtServer 造成伤害后,当前生命值按伤害计算结果减少。僵尸等许多实体默认会一直保留降低后的生命值,而玩家等实体可以恢复失去的生命值。
要获取或设置最大生命值,可直接读写属性:
// Get the attribute map of our entity.
AttributeMap attributes = entity.getAttributes();
// Get the max health of our entity.
float maxHealth = attributes.getValue(Attributes.MAX_HEALTH);
// Shortcut for the above.
maxHealth = entity.getMaxHealth();
// Setting the max health must either be done by getting the AttributeInstance and calling #setBaseValue, or by
// adding an attribute modifier. We will do the former here. Please refer to the Attributes article for more details.
attributes.getInstance(Attributes.MAX_HEALTH).setBaseValue(50);
受到伤害时,生物实体还会进行额外计算,例如考虑 minecraft:armor 属性,但属于 minecraft:bypasses_armor 标签的伤害类型除外,以及 minecraft:absorption 属性。还可以重写 #onDamageTaken 处理攻击后的行为;只有最终伤害大于零时才会调用它。
伤害事件
伤害处理流程较复杂,因此提供了多个可介入的事件,按下文列出的顺序触发。这些事件主要用于修改并非自己定义的实体所受伤害,例如 Minecraft 或其他 Mod 的实体,或修改所有实体受到的伤害。
所有事件共用 DamageContainer。攻击开始时创建新的容器,结束后丢弃。它保存原始 DamageSource、原始伤害,以及护甲、伤害吸收、附魔、状态效果等各项独立修改记录。容器会传给以下全部事件,可以检查已发生的修改,再按需处理。
EntityInvulnerabilityCheckEvent
这个事件可绕过或添加实体伤害免疫,也会对非生物实体触发。可以用它让实体免疫某种攻击,或移除已有免疫。
受技术条件限制,这个事件的处理应具有确定性,并且只依赖伤害类型。随机概率免疫、仅对一定伤害量生效的免疫等,应在下文的 LivingIncomingDamageEvent 中处理。
LivingIncomingDamageEvent
此事件仅在服务端触发,主要用于动态取消攻击,以及添加减伤修正回调。
动态取消攻击相当于添加非确定性的免疫,例如按概率取消伤害,或根据时间、伤害量决定是否免疫。固定不变的免疫应使用上面的 EntityInvulnerabilityCheckEvent。
减伤修正回调可修改某一部分减伤计算,例如让护甲减伤效果降低 50%。这一变化也会正确传递到后续状态效果等计算,使其基于变化后的伤害值运作。添加方式如下:
@SubscribeEvent // on the game event bus
public static void decreaseArmor(LivingIncomingDamageEvent event) {
// We only apply this decrease to players and leave zombies etc. unchanged
if (event.getEntity() instanceof Player) {
// Add our reduction modifier callback.
event.addReductionModifier(
// The reduction to target. See the DamageContainer.Reduction enum for possible values.
DamageContainer.Reduction.ARMOR,
// The modification to perform. Gets the damage container and the base reduction as inputs,
// and outputs the new reduction. Both input and output reductions are floats.
(container, baseReduction) -> baseReduction * 0.5f
);
}
}
回调按添加顺序应用,因此优先级更高的事件处理器添加的回调会先执行。
LivingShieldBlockEvent
此事件可全面自定义盾牌格挡,例如添加额外格挡、禁止格挡、修改原版判断、调整盾牌或攻击物品受到的耐久损耗、改变格挡视角范围、只挡弹射物不挡近战或反之、无需举盾的被动格挡,以及只挡住一定比例的伤害等。
这个事件不适合处理与盾牌类物品无关的免疫或攻击取消。
ArmorHurtEvent
该事件在计算攻击对护甲的耐久损耗时触发,可以修改哪件护甲损失多少耐久,也可以使其不损失耐久。
LivingDamageEvent.Pre
此事件在实际施加伤害前立即触发。此时 DamageContainer 已填充完毕,可获取最终伤害值;攻击已被视为成功,因此事件不能再取消。
此时可以利用全部修饰信息精细调整伤害量,但护甲耐久损耗等操作已经完成。
LivingDamageEvent.Post
此事件在伤害已施加、伤害吸收已扣减、战斗追踪器已更新,且统计与游戏事件处理完毕后触发。由于攻击已经发生,不能取消。它通常用于攻击后的效果。注意,即使伤害为零也会触发,必要时应检查该值。
如果处理的是自己的实体,可以考虑改为重写 ILivingEntityExtension#onDamageTaken()。与 LivingDamageEvent.Post 不同,它只在伤害大于零时调用。
状态效果
参阅状态效果与药水。
装备
参阅实体上的容器。
继承层级
生物实体的继承层级较复杂。如前所述,它有三个直接子类:
graph LR;
LivingEntity --> ArmorStand & Mob & Avatar;
class LivingEntity,Mob,Avatar yellow;
class ArmorStand blue;
黄色类是抽象类,蓝色类不是抽象类。
其中 ArmorStand 没有子类,也是唯一的非抽象类,因此下面主要介绍 Mob 和 Avatar 的继承层级。
Mob 的继承层级
Mob 的继承层级如下:
graph LR;
Mob --> AmbientCreature --> Bat;
Mob --> EnderDragon & Ghast & Phantom & PathfinderMob;
PathfinderMob --> AbstractGolem --> CopperGolem & IronGolem & Shulker & SnowGolem;
PathfinderMob --> AgeableMob;
AgeableMob --> AbstractVillager --> Villager & WanderingTrader;
AgeableMob --> AgeableWaterCreature;
AgeableWaterCreature --> Dolphin;
AgeableWaterCreature --> Squid --> GlowSquid;
AgeableMob --> Animal;
PathfinderMob --> Allay & Monster & WaterAnimal;
WaterAnimal --> AbstractFish;
AbstractFish --> AbstractSchoolingFish --> Cod & Salmon & TropicalFish;
AbstractFish --> Pufferfish & Tadpole;
Mob --> Slime --> MagmaCube;
class Mob,AmbientCreature,PathfinderMob,AbstractGolem,AgeableMob,AbstractVillager,AgeableWaterCreature,Animal,Monster,WaterAnimal,AbstractFish,AbstractSchoolingFish yellow;
class Bat,CopperGolem,EnderDragon,Ghast,Phantom,IronGolem,Shulker,SnowGolem,Villager,WanderingTrader,Dolphin,Squid,GlowSquid,Allay,Cod,Salmon,TropicalFish,Pufferfish,Tadpole,Slime,MagmaCube blue;
黄色类是抽象类,蓝色类不是抽象类。
图中未列出的其他生物实体都是 Animal 或 Monster 的子类。
这个层级看起来相当混乱。例如,蜜蜂、鹦鹉为什么不是飞行生物?继续查看 Animal 和 Monster 的子类时会更明显。本文不逐一展开,感兴趣时可以用 IDE 的“显示继承层级”功能查看。了解现状即可,无需过于纠结。
重点类包括:
PathfinderMob:包含寻路逻辑。AgeableMob:包含年龄增长和幼年实体逻辑。僵尸等有幼年变体的怪物不继承它,而是继承Monster。Animal:大多数动物的父类,还包含AbstractHorse、TamableAnimal等抽象子类。Monster:大多数怪物的抽象父类,也有AbstractPiglin、AbstractSkeleton、Raider、Zombie等抽象子类。WaterAnimal:鱼、鱿鱼、海豚等水生动物的抽象父类。寻路方式差别较大,因此与其他动物分开。
Avatar 的继承层级
Avatar 不仅定义玩家,还定义了类似玩家的假人。根据所在端使用不同的类。除 FakePlayer 和 Mannequin 外,通常都不应自行构造 Avatar。
graph LR;
Avatar --> Mannequin --> ClientMannequin;
Avatar --> Player;
Player --> AbstractClientPlayer --> LocalPlayer & RemotePlayer;
Player --> ServerPlayer --> FakePlayer;
class Avatar,Player,AbstractClientPlayer yellow;
class Mannequin,ClientMannequin,LocalPlayer,RemotePlayer,ServerPlayer,FakePlayer blue;
黄色类是抽象类,蓝色类不是抽象类。
AbstractClientPlayer:两种客户端玩家的基类,表示逻辑客户端上的玩家。LocalPlayer:表示当前运行游戏的玩家。RemotePlayer:表示多人游戏中LocalPlayer遇到的其他玩家,单人游戏不存在这类玩家。ServerPlayer:表示逻辑服务端上的玩家。FakePlayer:特殊的ServerPlayer子类,用于为需要玩家上下文的非玩家机制模拟玩家。Mannequin:可摆姿势的玩家形态实体,通常没有 AI。ClientMannequin:表示逻辑客户端上的假人。
生成
除 /summon 命令和代码中的 EntityType#spawn、Level#addFreshEntity 等常规方式外,Mob 还可通过其他方式生成。ArmorStand 使用常规方式即可;除 FakePlayer 外,不应自行实例化 Player。
刷怪蛋
通常会为生物注册一个刷怪蛋,但不是必需的。通过 SpawnEggItem 和 DataComponents#ENTITY_DATA 数据组件实现:
// Assume we have a DeferredRegister.Items called ITEMS
DeferredItem<SpawnEggItem> MY_ENTITY_SPAWN_EGG = ITEMS.registerItem("my_entity_spawn_egg",
properties -> new SpawnEggItem(
// The properties passed into the lambda.
// Using `spawnEgg` to set the DataComponent.
// This is done in the lambda to prevent the entity type from resolving before registration.
properties.spawnEgg(MY_ENTITY_TYPE.get())
));
它和其他物品一样,需要加入创造模式物品栏,并添加客户端物品定义、模型和翻译。
生成位置规则
默认情况下实体可在任意位置生成,包括方块内部和半空中。要限制这种情况,需要在 RegisterSpawnPlacementsEvent 中注册生成位置规则:
@SubscribeEvent // on the mod event bus
public static void registerSpawnPlacements(RegisterSpawnPlacementsEvent event) {
event.register(
// The EntityType<?> to register the spawn placement for.
MY_ENTITY.get(),
// SpawnPlacementType is a functional interface that checks whether a given position is valid
// for spawning. You can add your own SpawnPlacementType if needed,
// see the implementation of SpawnPlacementTypes for reference.
// Vanilla values include NO_RESTRICTIONS, IN_WATER, IN_LAVA and ON_GROUND.
SpawnPlacementTypes.ON_GROUND,
// The heightmap type to use. This provides the initial height to check during world generation.
// Possible values are WORLD_SURFACE, OCEAN_FLOOR, MOTION_BLOCKING and MOTION_BLOCKING_NO_LEAVES.
Heightmap.Types.WORLD_SURFACE,
// A predicate to check additional spawn rules, e.g. light. Can either be specified here
// as a lambda, or (more commonly) as a static method in the entity class.
(entityType, level, spawnReason, pos, random) -> {
// Make our mob only spawn on grass blocks.
return level.getBlockState(pos.below()).is(Blocks.GRASS_BLOCK)
|| Mob.checkMobSpawnRules(entityType, level, spawnReason, pos, random);
},
// For our own mobs, any operation may be used.
RegisterSpawnPlacementsEvent.Operation.OR
);
}
也可以修改原版或其他 Mod 的规则。Operation.AND 用于增加限制,Operation.OR 用于提供其他允许生成的条件,Operation.REPLACE 完全替换规则,替换时应使用低优先级事件监听器:
@SubscribeEvent // on the mod event bus
public static void registerSpawnPlacements(RegisterSpawnPlacementsEvent event) {
event.register(
// The EntityType<?> to register the spawn placement for.
MY_ENTITY.get(),
// When not using Operation.REPLACE, leave the spawn placement type
// and the heightmap type at null.
null,
null,
// Like above, check the additional/alternate spawn rules.
(entityType, level, spawnReason, pos, random) -> {
return level.getBlockState(pos.below()).is(Blocks.GRASS_BLOCK)
|| Mob.checkMobSpawnRules(entityType, level, spawnReason, pos, random);
},
// Use the appropriate Operation here.
RegisterSpawnPlacementsEvent.Operation.AND
);
}
事件还提供两个便捷重载,其中 spawnPredicate 是前面使用的 lambda:
@SubscribeEvent // on the mod event bus
public static void registerSpawnPlacements(RegisterSpawnPlacementsEvent event) {
// Identical to event.register(MY_ENTITY.get(), null, null, spawnPredicate, Operation.OR)
event.register(MY_ENTITY.get(), spawnPredicate);
// Identical to event.register(MY_ENTITY.get(), null, null, spawnPredicate, operation)
event.register(MY_ENTITY.get(), spawnPredicate, operation);
}
自然生成
另见:实体/MobCategory、世界生成/生物群系修改器/添加生成项、添加生成成本,以及 Minecraft Wiki 的生成周期。
原文描述:MobCategory#isFriendly() 为真的实体(默认所有非怪物)每刻尝试自然生成,为假的实体(所有怪物)每 400 刻,即 20 秒尝试一次。如果 MobCategory#isPersistent() 为真,主要是动物,区块生成时也会执行这一过程。
对于每个区块和生物分类,首先检查是否达到生成上限。具体而言,会检查周边已加载区块中,该分类实体数量是否小于 MobCategory#getMaxInstancesPerChunk() * loadedChunks / 289。loadedChunks 最多为以当前区块为中心的 17×17 区块范围;受视距等条件影响,实际已加载区块可能更少。
接下来,还需要至少有一位玩家,其周围该分类实体数量少于 MobCategory#getMaxInstancesPerChunk(),该区块才能生成这一分类的实体。这里“周围”指实体与玩家距离小于等于 128。
满足条件后,从相关生物群系的生成数据中随机选择一个条目,并尝试寻找合适位置。最多随机尝试三次,全部失败就不生成。
示例
下面以平原生物群系中的动物为例说明。
原文描述:平原生物群系每刻尝试生成 CREATURE 分类实体,该分类包含以下条目:
[
{"type": "minecraft:sheep", "minCount": 4, "maxCount": 4, "weight": 12},
{"type": "minecraft:pig", "minCount": 4, "maxCount": 4, "weight": 10},
{"type": "minecraft:chicken", "minCount": 4, "maxCount": 4, "weight": 10},
{"type": "minecraft:cow", "minCount": 4, "maxCount": 4, "weight": 8 },
{"type": "minecraft:horse", "minCount": 2, "maxCount": 6, "weight": 5 },
{"type": "minecraft:donkey", "minCount": 1, "maxCount": 3, "weight": 1 }
]
CREATURE 上限为 10,因此会检查以各玩家当前区块为中心、最多 17×17 范围中的其他 CREATURE 实体。如果找到的数量小于等于 10 * chunkCount / 289,基本相当于未加载区块附近更容易满足生成条件,再检查每个实体与最近玩家的距离。如果至少一个距离大于 128,就可以生成。
全部检查通过后,根据权重从上述列表选择一个生成条目。假设选中了猪,游戏会检查区块中的随机位置是否适合。合适时按条目的最小和最大数量生成,此例恰好为 4 头猪。不合适则再随机尝试两个位置;仍未找到就取消生成。
来源: NeoForged/Documentation(26.1)。中文翻译依据提交
4a680cdd4d15。