事件
Forge 使用事件总线,让 Mod 能够拦截原版和 Mod 的各种行为所产生的事件。例如,可以在玩家右键单击原版木棍时使用事件执行操作。
原创说明图,依据 Forge 1.21.x 官方文档 绘制。
大多数事件使用的主事件总线位于 MinecraftForge#EVENT_BUS。另有一个用于 Mod 专属事件的总线,位于 FMLJavaModLoadingContext#getModEventBus,只应在特定情况下使用,详见下文。每个事件都在其中一个总线上触发:大多数在 Forge 主事件总线上,部分在 Mod 专属事件总线上。事件处理器是注册到事件总线的方法。
创建事件处理器
事件处理方法只有一个参数且不返回结果;根据实现方式,它可以是静态方法或实例方法。
可通过 IEventBus#addListener 直接注册普通事件处理器,或通过 IEventBus#addGenericListener 注册泛型事件处理器(泛型事件继承 GenericEvent<T>)。这两种方法都接收表示方法引用的 consumer;泛型事件处理器还需指定泛型的类。处理器必须在 Mod 主类的构造函数中注册。
// 在主 Mod 类 ExampleMod 中
// 此事件位于 Mod 总线上
private void modEventHandler(RegisterEvent event) {
// 在此处理
}
// 此事件位于 Forge 总线上
private static void forgeEventHandler(AttachCapabilitiesEvent<Entity> event) {
// ...
}
// 在 Mod 构造函数中
modEventBus.addListener(this::modEventHandler);
forgeEventBus.addGenericListener(Entity.class, ExampleMod::forgeEventHandler);
带注解的实例事件处理器
下面的处理器监听 EntityItemPickupEvent。顾名思义,每当 Entity 捡起物品时,该事件就会被发布到事件总线。
public class MyForgeEventHandler {
@SubscribeEvent
public void pickupItem(EntityItemPickupEvent event) {
System.out.println("Item picked up!");
}
}
要注册处理器,使用 MinecraftForge.EVENT_BUS.register(...) 并传入包含处理器的类的实例。如果要注册到 Mod 专属事件总线,则使用 FMLJavaModLoadingContext.get().getModEventBus().register(...)。
带注解的静态事件处理器
处理器也可以是静态的,方法仍使用 @SubscribeEvent 注解,区别是另外标记为 static。注册静态处理器时,不能传入类实例,而必须传入 Class 本身。例如:
public class MyStaticForgeEventHandler {
@SubscribeEvent
public static void arrowNocked(ArrowNockEvent event) {
System.out.println("Arrow nocked!");
}
}
必须这样注册:MinecraftForge.EVENT_BUS.register(MyStaticForgeEventHandler.class)。
自动注册静态事件处理器
可以给类添加 @Mod$EventBusSubscriber 注解。@Mod 类构造时,该类会自动注册到 MinecraftForge#EVENT_BUS,基本等同于在 @Mod 类的构造函数末尾添加 MinecraftForge.EVENT_BUS.register(AnnotatedClass.class);。
可以向注解传入要监听的总线。建议同时指定 Mod ID 和注册目标总线:注解处理过程可能无法自行确定 Mod ID,显式指定总线也能提醒你检查是否使用了正确的总线。还可以指定加载订阅器的 Dist,即物理端,从而避免在独立服务器上加载客户端专属订阅器。
以下静态事件监听器监听 RenderLevelStageEvent,仅在客户端调用:
@Mod.EventBusSubscriber(modid = "mymod", bus = Bus.FORGE, value = Dist.CLIENT)
public class MyStaticClientOnlyEventHandler {
@SubscribeEvent
public static void drawLast(RenderLevelStageEvent event) {
System.out.println("Drawing!");
}
}
注意:这注册的是类本身,并非类实例,因此事件处理方法必须是静态的。
取消事件
可取消的事件带有 @Cancelable 注解,且 Event#isCancelable() 返回 true。可通过 Event#setCanceled(boolean canceled) 修改取消状态:传入 true 表示取消事件,传入 false 表示撤销取消。如果事件不可取消,无论传入哪个布尔值都会抛出 UnsupportedOperationException,因为不可取消事件的取消状态被视为不可变。
重要:并非所有事件都可以取消!尝试取消不可取消的事件会抛出未经检查的
UnsupportedOperationException,预期会导致游戏崩溃。取消前始终使用Event#isCancelable()检查。
结果
部分事件具有 Event$Result,可为以下三种值之一:DENY 停止事件;DEFAULT 使用原版行为;ALLOW 强制执行操作,不论原本是否会执行。通过在事件上调用 #setResult 并传入 Event$Result 设置结果。并非所有事件都有结果;有结果的事件带有 @HasResult 注解。
重要:不同事件可能以不同方式使用结果,使用前请查阅事件的 JavaDoc。
优先级
带有 @SubscribeEvent 注解的处理方法具有优先级,可设置注解的 priority 值。它可以是 EventPriority 枚举的任一值:HIGHEST、HIGH、NORMAL、LOW、LOWEST 和 MONITOR。HIGHEST 首先执行,随后按降序执行,MONITOR 最后执行。
MONITOR 是特殊优先级,在 LOWEST 之后运行,但不影响事件结果。在 MONITOR 阶段尝试取消或以其他方式修改事件对象可能抛出异常。它适用于日志和其他需要最后运行的只读操作;监听器被调用时可保证状态已经最终确定。
子事件
许多事件有不同变体,它们可能不同但围绕共同因素(例如 PlayerEvent),也可能表示具有多个阶段的事件(例如 PotionBrewEvent)。监听父事件类时,你的方法会收到所有子类的调用。
Mod 事件总线
Mod 事件总线主要用于监听 Mod 应当初始化的生命周期事件。总线上的每个事件都必须实现 IModBusEvent。许多事件并行运行,使 Mod 可以同时初始化。这意味着不能在这些事件中直接执行其他 Mod 的代码,应使用 InterModComms 系统。
Mod 初始化时在 Mod 事件总线上调用的四类最常用生命周期事件:
FMLCommonSetupEventFMLClientSetupEvent和FMLDedicatedServerSetupEventInterModEnqueueEventInterModProcessEvent
FMLClientSetupEvent 和 FMLDedicatedServerSetupEvent 仅在各自对应的发行端调用。这四类生命周期事件都是 ParallelDispatchEvent 的子类,因此并行运行。如果需要在任何 ParallelDispatchEvent 期间在主线程运行代码,可使用 #enqueueWork。
除生命周期事件外,Mod 事件总线上还有一些用于注册、设置或初始化各类内容的其他事件。与生命周期事件不同,它们大多不并行运行,例如:
RegisterColorHandlersEventModelEvent$BakingCompletedTextureStitchEventRegisterEvent
一个实用的判断原则是:如果事件应在 Mod 初始化期间处理,它通常在 Mod 事件总线上触发。
原文:Events。