交互
本页旨在让玩家左键点击、右键点击或中键点击各种事物这一相当复杂而令人困惑的过程变得更易理解,同时厘清在何处、为何应使用何种结果。
HitResult
为了确定玩家当前正看着什么,Minecraft 使用 HitResult。HitResult 在某种程度上等同于其他游戏引擎中的射线投射结果,其中最值得注意的是它包含一个 #getLocation 方法。
命中结果可以是三种类型之一,由 HitResult.Type 枚举表示:BLOCK、ENTITY 或 MISS。类型为 BLOCK 的 HitResult 可强制转换为 BlockHitResult,而类型为 ENTITY 的 HitResult 可强制转换为 EntityHitResult;这两种类型都提供了关于被命中的方块或实体的额外上下文。如果类型是 MISS,则表示既没有命中方块也没有命中实体,此时不应转换为任何一个子类。
在物理客户端上的每一帧,Minecraft 类都会更新并将当前所注视的 HitResult 存储在 hitResult 字段中。随后可通过 Minecraft.getInstance().hitResult 访问该字段。
左键点击物品
- 检查你主手中的
ItemStack所需的所有特性标志是否均已启用。如果此检查失败,流程结束。 - 如果
Player#cannotAttackWithItem(它会检查攻击延迟和DataComponents#MINIMUM_ATTACK_CHARGE)返回 false,流程结束。 - 以左键鼠标按钮和主手触发
InputEvent.InteractionKeyMappingTriggered。如果该事件被取消,流程结束。 - 根据你正看着什么(使用
Minecraft中的HitResult),会发生不同的事情:- 如果你手持一件带有某个
DataComponents#PIERCING_WEAPON的物品:- 仅服务端:调用
PiercingWeapon#attack。- 使用
ProjectileUtil#getHitEntitiesAlong获取所有满足以下条件的实体:- 处于攻击者的交互范围与物品的攻击范围(
DataComponents#ATTACK_RANGE)之内。 - 非无敌,且能被抛射物命中。
- 不是同一载具的乘客。
- 处于攻击者的交互范围与物品的攻击范围(
- 对每个实体调用
LivingEntity#stabAttack,若目标被成功造成伤害、击退或使其下座,则返回 true:- 伤害来源从
ItemStack#getDamageSource获取。 - 调用
Entity#hurtServer。 - 如果该穿刺武器造成击退,则调用
LivingEntity#causeExtraKnockback。 - 如果该穿刺武器在命中时使目标下座且目标是乘客,则调用
Entity#stopRiding。 - 如果目标是
LivingEntity,则调用ItemStack#hurtEnemy。 - 如果目标被成功造成伤害,则应用
EnchantmentHelper#doPostAttackEffects。 - 如果目标被成功造成伤害、击退或使其下座,则:
- 以目标实体调用
LivingEntity#setLastHurtMob。 - 调用
LivingEntity#playAttackSound。
- 以目标实体调用
- 伤害来源从
- 使用
- 调用
LivingEntity#onAttack。 - 调用
LivingEntity#lungerForwardMaybe。这会通过EnchantmentHelper#doLungeEffects应用突刺效果。 - 仅服务端:如果
LivingEntity#stabAttack至少对一个目标返回 true,则调用PiercingWeapon#makeHitSound。 - 仅服务端:调用
PiercingWeapon#makeSound。 - 调用
LivingEntity#swing。
- 仅服务端:调用
- 如果你正看着一个处于触及范围内的实体:
- 触发
AttackEntityEvent。如果该事件被取消,流程结束。 - 调用
IItemExtension#onLeftClickEntity。如果它返回 true,流程结束。 - 对目标调用
Entity#isAttackable。如果它返回 false,流程结束。 - 对目标调用
Entity#skipAttackInteraction。如果它返回 true,流程结束。 - 如果目标在
minecraft:redirectable_projectile标签中(默认包括火球和风弹)且是Projectile的实例,则目标被偏转,流程结束。 - 实体基础伤害(
minecraft:attack_damage属性的值)和附魔加成伤害分别计算为两个独立的浮点数。如果两者都为 0,流程结束。- 注意,这不包括来自主手物品的属性修饰符,它们会在该检查之后才被加上。
- 来自主手物品的
minecraft:attack_damage属性修饰符被加到基础伤害上。 - 触发
CriticalHitEvent。如果该事件的#isCriticalHit方法返回 true,则基础伤害乘以该事件#getDamageMultiplier方法返回的值——若一系列条件通过则默认为 1.5,否则为 1.0,但可被该事件修改。 - 附魔加成伤害被加到基础伤害上,得出最终伤害值。
- 触发
SweepAttackEvent。如果该事件的isSweeping方法返回 true,则玩家将执行横扫攻击。默认情况下,这会检查攻击冷却是否 > 90%、该攻击不是暴击、玩家在地面上且移动速度不快于其minecraft:movement_speed属性值。 - 调用
Entity#hurtOrSimulate。如果它返回 false,流程结束。 - 如果目标是
LivingEntity的实例,且攻击强度大于 90%、玩家正在疾跑、并且经附魔修改后的minecraft:attack_knockback属性值大于 0,则调用LivingEntity#knockback。- 在该方法内部,触发
LivingKnockBackEvent。如果该事件被取消,则不施加任何击退。
- 在该方法内部,触发
- 玩家基于
SweepAttackEvent#isSweeping对附近的LivingEntity执行横扫攻击。- 在该方法内部,如果实体处于玩家触及范围内且
Entity#hurtServer返回 true,则再次调用LivingEntity#knockback,这又会再次触发LivingKnockBackEvent。
- 在该方法内部,如果实体处于玩家触及范围内且
- 调用
Item#hurtEnemy。它可用于攻击后效果。例如,重锤会在此处(如适用)将玩家向上弹回空中。 - 调用
Item#postHurtEnemy。耐久度损耗在此处施加。- 如果耐久度归零,使堆叠变为
ItemStack#EMPTY,则触发PlayerDestroyItemEvent。
- 如果耐久度归零,使堆叠变为
- 触发
- 如果你正看着一个处于触及范围内的方块:
- 启动方块破坏子流程。
- 否则:
- 触发
PlayerInteractEvent.LeftClickEmpty。
- 触发
- 如果你手持一件带有某个
右键点击物品
在右键点击流程中,会调用若干返回两种结果类型之一(见下文)的方法。这些方法大多在返回明确的成功或明确的失败时取消流程。为了便于阅读,从现在起,这种“明确的成功或明确的失败”将被称为“决定性结果”。
- 以右键鼠标按钮和主手触发
InputEvent.InteractionKeyMappingTriggered。如果该事件被取消,流程结束。 - 检查若干条件,例如你不处于旁观模式,或你主手中的
ItemStack所需的所有特性标志均已启用。如果其中至少一项检查失败,流程结束。 - 根据你正看着什么(使用
Minecraft中的HitResult),会发生不同的事情:- 如果你正看着一个处于触及范围内、且不在世界边界之外的实体:
- 触发
PlayerInteractEvent.EntityInteractSpecific。如果该事件被取消,流程结束。 - 对你正看着的实体调用
Entity#interactAt。如果它返回决定性结果,流程结束。- 如果你想为自己的实体添加行为,重写此方法。如果你想为原版实体添加行为,使用该事件。
- 如果该实体打开了某个界面(例如村民交易 GUI 或矿车箱子 GUI),流程结束。
- 触发
PlayerInteractEvent.EntityInteract。如果该事件被取消,流程结束。 - 对你正看着的实体调用
Entity#interact。如果它返回决定性结果,流程结束。- 如果你想为自己的实体添加行为,重写此方法。如果你想为原版实体添加行为,使用该事件。
- 对于
Mob,Entity#interact的重写会处理诸如拴绳,以及当你主手中的ItemStack是刷怪蛋时生成幼崽等事务,然后把生物专属的处理委托给Mob#mobInteract。Entity#interact的结果规则在此同样适用。
- 如果你正看着的实体是
LivingEntity,则对你主手中的ItemStack调用Item#interactLivingEntity。如果它返回决定性结果,流程结束。
- 触发
- 如果你正看着一个处于触及范围内、且不在世界边界之外的方块:
- 触发
PlayerInteractEvent.RightClickBlock。如果该事件被取消,流程结束。你也可以在此事件中专门只拒绝方块用途或物品用途。 - 调用
IItemExtension#onItemUseFirst。如果它返回决定性结果,流程结束。 - 如果
IItemExtension#doesSneakBypassUse返回 false 且该事件未拒绝方块用途,则触发UseItemOnBlockEvent。如果该事件被取消,则使用其取消结果。否则,调用BlockBehaviour#useItemOn。如果它返回决定性结果,流程结束。 - 如果该
InteractionResult是TryEmptyHandInteraction的实例(例如TRY_WITH_EMPTY_HAND)且执行手是主手,则调用BlockBehaviour#useWithoutItem。如果它返回决定性结果,流程结束。 - 如果该事件未拒绝物品用途,则调用
Item#useOn。如果它返回决定性结果,流程结束。
- 触发
- 否则:
- 触发
PlayerInteractEvent.RightClickEmpty。
- 触发
- 如果你正看着一个处于触及范围内、且不在世界边界之外的实体:
- 触发
PlayerInteractEvent.RightClickItem。如果该事件被取消,流程结束。 - 调用
Item#use。- 如果该
InteractionResult是Success的实例(例如SUCCESS),则ItemStack被更改为Success#heldItemTransformedTo。
- 如果该
- 如果当前堆叠与原始堆叠不匹配且新堆叠为空,则触发
PlayerDestroyItemEvent。 - 上述过程会再运行一次,这次使用副手而非主手。
InteractionResult
InteractionResult 是一个密封接口,表示某个物品或空手与某个对象(例如实体、方块等)之间某次交互的结果。该接口划分为四个 record,共有六种潜在的默认状态。
首先是 InteractionResult.Success,表示该操作应被视为成功,从而结束流程。成功状态有两个参数:SwingSource,指示实体是否应在相应的逻辑端挥动手臂;以及 InteractionResult.ItemContext,它保存了此次交互是否由手持物品引起,以及手持物品在使用后转变成了什么。挥手来源由某个默认状态决定:InteractionResult#SUCCESS 表示客户端挥手,InteractionResult#SUCCESS_SERVER 表示服务端挥手,InteractionResult#CONSUME 表示不挥手。若 ItemStack 发生了变化,则通过 Success#heldItemTransformedTo 设置物品上下文;若手持物品与对象之间并无交互,则用 withoutItem。默认设置为存在物品交互但没有发生转变。
// In some method that returns an interaction result
// Item in hand will turn into an apple
return InteractionResult.SUCCESS.heldItemTransformedTo(new ItemStack(Items.APPLE));
SUCCESS 和 SUCCESS_SERVER 一般绝不应在同一个方法中使用。如果客户端有足够的信息来判断何时挥手,则应始终使用 SUCCESS。否则,如果它依赖客户端上不存在的服务端信息,则应使用 SUCCESS_SERVER。
其次是 InteractionResult.Fail,由 InteractionResult#FAIL 实现,表示该操作应被视为失败,不允许再发生进一步的交互。流程将结束。它可以在任何地方使用,但在 Item#useOn 和 Item#use 之外应谨慎使用。在许多情况下,使用 InteractionResult#PASS 更为合理。
最后是 InteractionResult.Pass 和 InteractionResult.TryWithEmptyHandInteraction,分别由 InteractionResult#PASS 和 InteractionResult#TRY_WITH_EMPTY_HAND 实现。这些 record 表示某个操作既不应被视为成功也不应被视为失败,流程应继续进行。PASS 是所有 InteractionResult 方法的默认行为,唯有 BlockBehaviour#useItemOn 例外,它返回 TRY_WITH_EMPTY_HAND。更具体地说,如果 BlockBehaviour#useItemOn 返回除 TRY_WITH_EMPTY_HAND 之外的任何值,则无论物品是否在主手中,都不会调用 BlockBehaviour#useWithoutItem。
有些方法具有特殊行为或要求,将在下面的章节中说明。
Item#useOn
如果你希望该操作被视为成功,但又不想让手臂挥动或授予一个 ITEM_USED 统计点,请使用 InteractionResult#CONSUME 并调用 #withoutItem。
// In Item#useOn
return InteractionResult.CONSUME.withoutItem();
Item#use
这是唯一一处会使用来自 Success 变体(SUCCESS、SUCCESS_SERVER、CONSUME)的已转变 ItemStack 的场合。由 Success#heldItemTransformedTo 设置的结果 ItemStack,会在其发生变化时替换发起使用时所用的 ItemStack。
Item#use 的默认实现:当物品可食用(具有 DataComponents#CONSUMABLE)且玩家可以吃下该物品(因为他们处于饥饿状态,或因为该物品总是可食用)时返回 InteractionResult#CONSUME;当物品可食用(具有 DataComponents#CONSUMABLE)但玩家无法吃下该物品时返回 InteractionResult#FAIL。如果物品可装备(具有 DataComponents#EQUIPPABLE),则在切换时返回 InteractionResult#SUCCESS,且手持物品被替换为切换来的物品(通过 heldItemTransformedTo);如果盔甲上的附魔具有 EnchantmentEffectComponents#PREVENT_ARMOR_CHANGE 组件,则返回 InteractionResult#FAIL。如果物品可以格挡攻击(具有 DataComponents#BLOCKS_ATTACKS),则在返回 InteractionResult#CONSUME 之前会调用 Item#startUsingItem。否则返回 InteractionResult#PASS。
在此处考虑主手时返回 InteractionResult#FAIL,将阻止副手行为运行。如果你希望副手行为运行(通常你会希望如此),则应改为返回 InteractionResult#PASS。
中键点击
- 如果
Minecraft.getInstance().hitResult中的HitResult为 null 或类型为MISS,流程结束。 - 以左键鼠标按钮和主手触发
InputEvent.InteractionKeyMappingTriggered。如果该事件被取消,流程结束。 - 根据你正看着什么(使用
Minecraft.getInstance().hitResult中的HitResult),会发生不同的事情:- 如果你正看着一个处于触及范围内的实体:
- 如果
Entity#isPickable返回 false,流程结束。 - 如果
Player#isWithinEntityInteractionRange返回 false,流程结束。 - 调用
Entity#getPickResult。如果存在与所得ItemStack匹配的快捷栏槽位,则将该槽位设为激活。否则,如果玩家处于创造模式,则将所得ItemStack加入玩家物品栏。- 默认情况下,此方法转发至
Entity#getPickResult,Mod 开发者可重写它。
- 默认情况下,此方法转发至
- 如果
- 如果你正看着一个处于触及范围内的方块:
- 如果
Player#isWithinBlockInteractionRange返回 false,流程结束。 - 调用
IBlockExtension#getCloneItemStack(它默认委托给BlockBehaviour#getCloneItemStack),其返回值成为“选中的”ItemStack。- 默认情况下,这会返回
Block的Item表示。
- 默认情况下,这会返回
- 如果按住了 Control 键、玩家处于创造模式、且目标方块拥有一个
BlockEntity:BlockEntity的数据从BlockEntity#saveCustomOnly获取- 作为后处理步骤,调用
BlockEntity#removeComponentsFromTag。
- 作为后处理步骤,调用
BlockEntity的数据通过DataComponents#BLOCK_ENTITY_DATA被加入“选中的”ItemStack。
- 如果存在与“选中的”
ItemStack匹配的快捷栏槽位,则将该槽位设为激活。否则,如果玩家处于创造模式,则将“选中的”ItemStack加入玩家物品栏。
- 如果
- 如果你正看着一个处于触及范围内的实体: