Skip to main content
Version: 1.21.4

交互(Interactions)

本页旨在帮助你理解玩家进行左键、右键或中键点击时,背后相对复杂且容易混淆的处理流程,并澄清在不同场景下应使用哪种结果(result)、原因何在。

HitResult

为了判断玩家当前正在注视什么,Minecraft 会使用一个 HitResultHitResult 在其他游戏引擎中大致等同于射线检测(ray cast)的结果,其中最重要的方法是 #getLocation

一个 hit result 可以有三种类型,这三种类型由 HitResult.Type 枚举(enum)表示:BLOCKENTITYMISS。类型为 BLOCKHitResult 可以强制转换为 BlockHitResult,类型为 ENTITY 的可以强制转换为 EntityHitResult;这两种类型都能提供更多关于被击中的 方块实体 的上下文信息。如果类型为 MISS,表示既没有击中方块也没有击中实体,这种情况不应强制转换为上述任一子类。

在每一帧的 物理客户端Minecraft 类会更新并存储当前玩家注视的 HitResult,保存在 hitResult 字段中。你可以通过 Minecraft.getInstance().hitResult 访问到这个字段。

左键点击物品(Left-Clicking an Item)

  • 会检查你主手中的 ItemStack 是否启用了所有必需的 功能标志。如果此检查未通过,流程(pipeline)将终止。
  • 会以左键和主手触发 InputEvent.InteractionKeyMappingTriggered 事件(event)。如果该 事件取消,流程将终止。
  • 根据你当前所观察的内容(在 Minecraft 中使用 HitResult),会有不同的行为发生:
    • 如果你正在观察一个在你攻击范围内的 实体
      • 会触发 AttackEntityEvent 事件。如果该事件被取消,流程终止。
      • 调用 IItemExtension#onLeftClickEntity 方法。如果返回 true,流程终止。
      • 对目标调用 Entity#isAttackable 方法。如果返回 false,流程终止。
      • 对目标调用 Entity#skipAttackInteraction 方法。如果返回 true,流程终止。
      • 如果目标在 minecraft:redirectable_projectile 标签中(默认包括火球和风之弹),并且是 Projectile 的实例,则该目标会被弹开,流程终止。
      • 计算实体基础伤害(minecraft:generic.attack_damage 属性 的数值)和附魔额外伤害,两者分别为 float。如果两者均为 0,流程终止。
        • 注意:这一步不会计算主手物品的 属性修饰符,这些会在后续步骤中添加。
      • 将主手物品上的 minecraft:generic.attack_damage 属性修饰符加到基础伤害上。
      • 触发 CriticalHitEvent 事件。如果事件的 #isCriticalHit 方法返回 true,则基础伤害会乘以事件 #getDamageMultiplier 方法返回的数值(如果满足 一系列条件,默认是 1.5,否则为 1.0,该数值也可以被事件修改)。
      • 将附魔额外伤害加到基础伤害上,得到最终伤害值。
      • 调用 Entity#hurt 方法。如果返回 false,流程终止。
      • 如果目标是 LivingEntity 的实例,则调用 LivingEntity#knockback 方法。
        • 在该方法内部,会触发 LivingKnockBackEvent 事件。
      • 如果攻击冷却 > 90%,攻击不是暴击,玩家在地面上,且移动速度不超过其 minecraft:generic.movement_speed 属性值,则会对附近的 LivingEntity 进行横扫攻击。
        • 在该方法内部,会再次调用 LivingEntity#knockback,进而第二次触发 LivingKnockBackEvent 事件。
      • 调用 Item#hurtEnemy 方法。该方法可用于攻击后的效果处理。例如,锤矛(mace)会在此处将玩家弹回空中(如果适用)。
      • 调用 Item#postHurtEnemy 方法。在这里会应用耐久度损耗。
    • 如果你正在观察一个在你攻击范围内的 方块
    • 否则:
      • 会触发 PlayerInteractEvent.LeftClickEmpty 事件。

右键点击物品(Right-Clicking an Item)

在右键点击(right-clicking)流程中,会调用若干返回两种结果类型之一(见下文)的方法(methods)。这些方法中的大多数,如果返回了明确的成功(explicit success)或明确的失败(explicit failure),就会中断整个流程。为了便于阅读,本文将这种“明确的成功或失败”称为“确定性结果(definitive result)”。

  • 当按下鼠标右键并使用主手时,会触发 InputEvent.InteractionKeyMappingTriggered 事件(event)。如果该 事件取消,则后续处理流程会立即终止。
  • 系统会检查若干条件,例如玩家是否处于旁观者模式(spectator mode),以及主手中的 ItemStack 是否启用了所有所需的 功能标志。只要有一项检查未通过,整个处理流程也会终止。
  • 根据你所观察的对象(使用 Minecraft 中的 HitResult),会触发不同的流程:
    • 如果你正在观察一个 实体,并且该实体在你的可达范围内且不在世界边界之外:
      • 会触发 PlayerInteractEvent.EntityInteractSpecific 事件。如果该事件被取消,流程终止。
      • 会调用 你正在观察的实体上的 Entity#interactAt 方法。如果该方法返回了明确的结果,流程终止。
        • 如果你想为你自己的实体添加行为,可以重写此方法。如果你想为原版实体添加行为,应使用事件(event)。
      • 如果该实体会打开一个界面(例如村民交易界面或矿车箱 GUI),流程终止。
      • 会触发 PlayerInteractEvent.EntityInteract 事件。如果该事件被取消,流程终止。
      • 会调用 你正在观察的实体上的 Entity#interact 方法。如果该方法返回了明确的结果,流程终止。
        • 如果你想为你自己的实体添加行为,可以重写此方法。如果你想为原版实体添加行为,应使用事件(event)。
        • 对于 MobEntity#interact 的重写会处理如牵引(leashing)和当主手中的 ItemStack 是生成蛋(spawn egg)时生成幼崽等操作,然后将特定于生物的处理交给 Mob#mobInteractEntity#interact 的返回结果规则在这里同样适用。
      • 如果你正在观察的实体是 LivingEntity,会在你主手中的 ItemStack 上调用 Item#interactLivingEntity。如果它返回了明确的结果,流程终止。
    • 如果你正在观察一个 方块,并且该方块在你的可达范围内且不在世界边界之外:
      • 会触发 PlayerInteractEvent.RightClickBlock 事件。如果该事件被取消,流程终止。你也可以在此事件中特别拒绝方块或物品的使用。
      • 会调用 IItemExtension#onItemUseFirst。如果它返回了明确的结果,流程终止。
      • 如果玩家没有潜行,并且事件没有拒绝方块的使用,会触发 UseItemOnBlockEvent。如果事件被取消,则使用取消时的结果。否则会调用 Block#useItemOn。如果它返回了明确的结果,流程终止。
      • 如果 InteractionResultTRY_WITH_EMPTY_HAND,且执行的手是主手,则会调用 Block#useWithoutItem。如果它返回了明确的结果,流程终止。
      • 如果事件没有拒绝物品的使用,会调用 Item#useOn。如果它返回了明确的结果,流程终止。
  • 会调用 Item#use。如果它返回了明确的结果,流程终止。
  • 上述流程会再次运行一次,这次使用的是副手而不是主手。

InteractionResult

InteractionResult 是一个密封接口(sealed interface),用于表示物品或空手与某个对象(如实体、方块等)交互后的结果。该接口被拆分为四个记录类型(record),并包含六种可能的默认状态。

首先是 InteractionResult.Success,表示操作应被视为成功,并终止后续处理流程(pipeline)。成功状态包含两个参数:SwingSource,用于指示实体是否应在对应的 逻辑侧(logical side) 进行挥动动作;以及 InteractionResult.ItemContext,用于记录这次交互是否由持有物品引发,以及物品在使用后变成了什么。挥动来源由默认状态决定:InteractionResult#SUCCESS 代表客户端挥动,InteractionResult#SUCCESS_SERVER 代表服务端挥动,InteractionResult#CONSUME 则表示不挥动。物品上下文(item context)可以通过 Success#heldItemTransformedTo 方法设置(如果 ItemStack 发生了变化),或者通过 withoutItem 设置(如果持有物品与对象之间没有交互)。默认情况下,系统会认为发生了物品交互,但物品本身没有变化。

// 在某个返回交互结果的方法中

// 手中的物品将变成一个苹果
return InteractionResult.SUCCESS.heldItemTransformedTo(new ItemStack(Items.APPLE));
note

SUCCESSSUCCESS_SERVER 通常不应在同一个方法中同时使用。如果客户端有足够的信息判断何时挥动,则应始终使用 SUCCESS。否则,如果需要依赖客户端无法获取的服务端信息,则应使用 SUCCESS_SERVER

接下来是 InteractionResult.Fail,由 InteractionResult#FAIL 实现,表示操作应被视为失败,不允许进行后续交互,流程将被终止。该状态可以在任何地方使用,但在 Item#useOnItem#use 之外应谨慎使用。在许多情况下,使用 InteractionResult#PASS 更为合理。

最后是 InteractionResult.PassInteractionResult.TryWithEmptyHandInteraction,分别由 InteractionResult#PASSInteractionResult#TRY_WITH_EMPTY_HAND 实现。这两个记录类型表示操作既不算成功也不算失败,流程应继续。PASS 是所有 InteractionResult 方法的默认行为,除了 BlockBehaviour#useItemOn,它返回的是 TRY_WITH_EMPTY_HAND。更具体地说,如果 BlockBehaviour#useItemOn 返回的不是 TRY_WITH_EMPTY_HAND,那么无论主手是否有物品,BlockBehaviour#useWithoutItem 都不会被调用。

部分方法具有特殊行为或要求,具体将在下方章节中说明。

Item#useOn

如果你希望操作被视为成功,但又不希望手臂挥动或获得 ITEM_USED 统计点,应使用 InteractionResult#CONSUME 并调用 #withoutItem

// 在 Item#useOn 方法中
return InteractionResult.CONSUME.withoutItem();

Item#use

这是唯一一种会用到 Success 变体(SUCCESSSUCCESS_SERVERCONSUME)中变换后的 ItemStack 的情况。如果 Success#heldItemTransformedTo 设置了新的 ItemStack,并且和最初使用时的 ItemStack 不同,那么它会替换原本的 ItemStack

Item#use 的默认实现会在物品可食用(具有 DataComponents#CONSUMABLE)且玩家可以食用该物品(比如玩家饿了,或者该物品总是可食用)时,返回 InteractionResult#CONSUME;如果物品可食用但玩家不能吃,则返回 InteractionResult#FAIL。如果物品可装备(具有 DataComponents#EQUIPPABLE),则在与主手物品交换时返回 InteractionResult#SUCCESS(通过 heldItemTransformedTo 替换主手物品),如果装备上的附魔具有 EnchantmentEffectComponents#PREVENT_ARMOR_CHANGE 组件,则返回 InteractionResult#FAIL。否则,返回 InteractionResult#PASS

如果在这里返回 InteractionResult#FAIL,并且正在考虑主手(main hand),那么副手(offhand)的行为将不会被执行。如果你希望副手的行为也能运行(这通常是你想要的效果),应返回 InteractionResult#PASS

中键点击(Middle-Clicking)

  • 如果 Minecraft.getInstance().hitResult 中的 HitResult 为空或类型为 MISS,则流程终止。
  • 触发 InputEvent.InteractionKeyMappingTriggered,使用左键和主手。如果该 事件取消,流程终止。
  • 根据你所看到的内容(即 Minecraft.getInstance().hitResult 中的 HitResult),会发生不同的事情:
    • 如果你正在看一个在可达范围内的 实体
      • 如果 Entity#isPickable 返回 false,流程终止。
      • 如果你不在创造模式,流程终止。
      • 调用 IEntityExtension#getPickedResult。返回的 ItemStack 会被添加到玩家背包中。
        • 默认情况下,该方法会转发到 Entity#getPickResult,模组开发者可以重写这个方法。
    • 如果你正在看一个在可达范围内的 方块
      • 调用 Block#getCloneItemStack,并将其作为“选中的” ItemStack
        • 默认情况下,这会返回该 BlockItem 形式。
      • 如果按住 Control 键,且玩家处于创造模式,并且目标方块有 BlockEntity,则该 BlockEntity 的数据会被添加到“选中的” ItemStack 上。
      • 如果玩家处于创造模式,“选中的” ItemStack 会被添加到玩家背包中。否则,如果热键栏中存在与“选中”物品相同的物品,则该栏位会被激活。

属性 属性修饰符 方块 破坏方块 方块实体 可取消事件 暴击 效果 实体 事件 功能标志 命中结果 伤害 物品堆 物品使用于 生物实体 物理侧 逻辑侧