交互(Interactions)
本页旨在帮助你理解玩家进行左键、右键或中键点击时,背后相对复杂且容易混淆的处理流程,并澄清在不同场景下应使用哪种结果(result)、原因何在。
HitResult
为了判断玩家当前正在注视什么,Minecraft 会使用一个 HitResult。HitResult 在其他游戏引擎中大致等同于射线检测(ray cast)的结果,其中最重要的方法是 #getLocation。
一个 hit result 可以有三种类型,这三种类型由 HitResult.Type 枚举(enum)表示:BLOCK、ENTITY 或 MISS。类型为 BLOCK 的 HitResult 可以强制转换为 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)。
- 对于
Mob,Entity#interact的重写会 处理如牵引(leashing)和当主手中的ItemStack是生成蛋(spawn egg)时生成幼崽等操作,然后将特定于生物的处理交给Mob#mobInteract。Entity#interact的返回结果规则在这里同样适用。
- 如果你正在观察的实体是
LivingEntity,会在你主手中的ItemStack上调用Item#interactLivingEntity。如果它返回了明确的结果,流程终止。
- 会触发
- 如果你正在观察一个 方块,并且该方块在你的可达范围内且不在世界边界之外:
- 会触发
PlayerInteractEvent.RightClickBlock事件。如果该事件被取消,流程终止。你也可以在此事件中特别拒绝方块或物品的使用。 - 会调用
IItemExtension#onItemUseFirst。如果它返回了明确的结果,流程终止。 - 如果玩家没有潜行,并且事件没有拒绝方块的使用,会触发
UseItemOnBlockEvent。如果事件被取消,则使用取消时的结果。否则会调用Block#useItemOn。如果它返回了明确的结果,流程终止。 - 如果
InteractionResult为TRY_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));
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 实现。这两个记录类型表示操作既不算成功也不算失败,流程应继续。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 变体(SUCCESS、SUCCESS_SERVER、CONSUME)中变换后的 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。- 默认情况下,这会返回该
Block的Item形式。
- 默认情况下,这会返回该
- 如果按住 Control 键,且玩家处于创造模式,并且目标方块有
BlockEntity,则该BlockEntity的数据会被添加到“选中的”ItemStack上。 - 如果玩家处于创造模式,“选中的”
ItemStack会被添加到玩家背包中。否则,如果热键栏中存在与“选中”物品相同的物品,则该栏位会被激活。
- 调用
- 如果你正在看一个在可达范围内的 实体:
属性 属性修饰符 方块 破坏方块 方块实体 可取消事件 暴击 效果 实体 事件 功能标志 命中结果 伤害 物品堆 物品使用于 生物实体 物理侧 逻辑侧