我的世界浅谈Forge的事件系统和使用
前言:读者应该具有一定的Java基础
Forge事件系统
MC原版并没有事件这种东西,事件系统是Forge提供的修改添减原版内容,特性的东西。
Forge的事件系统覆盖面极广,模组注册,世界生成,玩家行为,渲染等。
例如:当玩家刻更新时的事件(TickEvent.PlayerTickEvent)。
绝大多数的事件都是在主事件总线上触发(MinecraftForge.EVENT_BUS)。
使用
将事件订阅到事件总线上。
EventBus提供的shutdown()方法是来关闭总线的。
创建Event Handler
事件方法是void类型的,不返回结果。该方法可以是静态(static void)或实例(void)的。
注册方法:
值得一提的是,泛型事件处理程序也需要指定泛型的类。必须在 main mod 类的构造函数中注册事件处理程序。
实例事件
public class MyForgeEventHandler {
@SubscribeEvent
public void pickupItem(EntityItemPickupEvent event) {
System.out.println("Item picked up!");
}
}
我们先从实例的方法开始说起。可以看到这里最为特殊的就是@SubscribeEvent注解,它用来标记这是一个订阅器,至于它具体监听的事件是由它的参数类型决定的,在这里它的参数类型是EntityItemPickupEvent,说明它监听的是实体捡起物品这个事件。
当然,对于实例方式的事件处理这样还不够,我们还得手动在某个地方实例化它并把它注入到事件总线里,我们之前说过Minecraft里有两条事件总线「Forge总线」和「Mod总线」,Mod总线主要负责游戏的生命周期事件,也就是初始化过程的事件,而Forge总线负责的就是除了生命周期事件外的所有事件。你可以用MinecraftForge.EVENT_BUS.register()方法将你的事件实例注册到Forge总线中,也可用FMLJavaModLoadingContext.get().getModEventBus().register()方法将其注册到Mod总线中,一般情况下你应该在你的Mod主类的初始化方法里注册这些事件。
在我们的例子里就是如下:
MinecraftForge.EVENT_BUS.register(new MyForgeEventHandler());
静态事件
当然所有的事件处理器都要手动注册非常的麻烦,Forge同样提供了一个静态的注册事件的方法。内容如下:
@Mod.EventBusSubscriber
public class MyStaticClientOnlyEventHandler {
@SubscribeEvent
public static void drawLast(RenderLevelLastEvent event) {
System.out.println("Drawing!");
}
}
可以看到,这里与实例注册不同的是增加了@Mod.EventBusSubscriber,他会自动注册类下带有 @SubscribeEvent注解的静态方法。
你可以用@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)来指定你要注入到Mod总线中。当然这里的参数不止一个,大家可以自行查看@Mod.EventBusSubscriber的具体内容。
更为详细的使用事件方法请看 早上 的Forge事件机制浅谈。
子事件
例如:TickEvent下有PlayerTickEvent,ClientTickEvent等
许多事件本身有不同的变体。这些可以是不同的,但都基于一个共同的因素(例如),或者可以是具有多个阶段的事件(例如)。请注意,如果侦听父事件类,则将收到对所有子类的方法的调用。
模组事件总线
mod 事件总线主要用于侦听 mods 应初始化的生命周期事件。mod 总线上的每个事件都需要实现 。其中许多事件也是并行运行的,因此可以同时初始化模组。这确实意味着您无法在这些事件中直接执行来自其他模组的代码。为此使用系统。IModBusEvent InterModComms
以下是在 mod 事件总线上的 mod 初始化期间调用的四个最常用的生命周期事件:
FMLCommonSetupEvent
FMLClientSetupEvent & FMLDedicatedServerSetupEvent
InterModEnqueueEvent
InterModProcessEvent
注意
和 仅在其各自的分发上调用。FMLClientSetupEvent FMLDedicatedServerSetupEvent。
这几个生命周期事件都是并行运行的,因为它们都是 的子类。如果要在任何 期间在主线程上运行运行代码,可以使用 来执行此操作。 ParallelDispatchEvent ParallelDispatchEvent#enqueueWork
在生命周期事件旁边,有一些杂项事件在 mod 事件总线上触发,您可以在其中注册、设置或初始化各种内容。与生命周期事件相比,这些事件中的大多数不是并行运行的。举个典型例子:RegisterEvent
这里有一个很好的经验法则:当事件应该在 mod 初始化期间处理时,会在 mod 事件总线上触发事件。
Event类解析
Forge提供的所有事件,都是Event类的子类。当前Forge版本下这个类的所有的子类的用法zzzz大佬在他的教程附录中列出了一张表,以供读者参考。这一部分针对Event类本身。
Event类添加了下面几个公开方法:
· public boolean isCancelable()
返回该事件是否可以被取消。
· public boolean isCanceled()
返回该事件是否已被取消。
· public void setCanceled(boolean cancel)
设置该事件是否被取消。
· public boolean hasResult()
返回该事件是否有结果,添加了@HasResult注解的事件默认为true,否则为false。
· public Result getResult()
返回该事件的结果,有Result.DENY,Result.DEFAULT,Result.ALLOW三种,默认为Result.DEFAULT。
· public void setResult(Result value)
为该事件设置一个结果。
· public ListenerList getListenerList()
获取所有注册该事件的监听器。
· public EventPriority getPhase()
获取该事件的优先级,上面已有说明。
· public void setPhase(EventPriority value)
设置该事件的优先级,上面已有说明。
(以上大部分来自zzzz的教程)
自定义一个事件并使用它
有些时候,Forge本身提供的事件不能满足模组制作者们的需求,这时候就可以自定义事件。
我们新建一个UseItemEvent 事件。
public class UseItemEvent extends MegaItemEvent{
private final InteractionHand interactionHand;
private final Level level;
public UseItemEvent(Player player, ItemStack stack, InteractionHand hand, Level level) {
super(player, stack);
interactionHand = hand;
this.level = level;
}
public InteractionHand getInteractionHand() {
return interactionHand;
}
public boolean isClient() {
return level.isClientSide;
}
public Level getLevel() {
return level;
}
}
可以看到,这个事件类提供了两个getter方法。
MegaItemEvent.java下。
public class MegaItemEvent extends PlayerEvent {
private ItemStack stack;
public MegaItemEvent(Player player, ItemStack itemStack) {
super(player);
stack = itemStack;
}
public ItemStack getStack() {
return stack;
}
public void setStack(ItemStack stack) {
this.stack = stack;
}
}
接下来我们需要在指定位置发布事件(post)。
使用Mixin注入useItem方法(右键物品时)。
@Mixin(MultiPlayerGameMode.class)
public class MultiPlayerGameModeMixin {
@Inject(method = "useItem", at = @At("HEAD"))
public void useItem(Player p_105236_, Level p_105237_, InteractionHand p_105238_, CallbackInfoReturnable<InteractionResult> cir) {
UseItemEvent event = new UseItemEvent(p_105236_, p_105236_.getItemInHand(p_105238_), p_105238_, p_105237_);
MinecraftForge.EVENT_BUS.post(event);
}
}
(当然,你也可以自定义一个EventBus来用)。
Events.java下。
@SubscribeEvent
public static void use(UseItemEvent event) {
if (event.getPlayer().getItemInHand(event.getInteractionHand()).getItem() instanceof SwordItem)
event.getPlayer().setItemInHand(event.getInteractionHand(), ItemStack.EMPTY);
}
这个事件的意思就是,当你右键一个剑类物品,就会清除它。
可以看到事件成功运行,物品被清除。
创建setter并使用
UseItemEvent.java下。
public class UseItemEvent extends MegaItemEvent{
private InteractionHand interactionHand;
private final Level level;
public UseItemEvent(Player player, ItemStack stack, InteractionHand hand, Level level) {
super(player, stack);
interactionHand = hand;
this.level = level;
}
public InteractionHand getInteractionHand() {
return interactionHand;
}
public boolean isClient() {
return level.isClientSide;
}
public Level getLevel() {
return level;
}
//新建setter
public void setHand(InteractionHand hand) {
this.interactionHand = hand;
}
}
MultiPlayerGameModeMixin.java下。
@Mixin(MultiPlayerGameMode.class)
public class MultiPlayerGameModeMixin {
@Inject(method = "useItem", at = @At("HEAD"))
public void useItem(Player p_105236_, Level p_105237_, InteractionHand p_105238_, CallbackInfoReturnable<InteractionResult> cir) {
UseItemEvent event = new UseItemEvent(p_105236_, p_105236_.getItemInHand(p_105238_), p_105238_, p_105237_);
MinecraftForge.EVENT_BUS.post(event);
//设置
p_105238_ = event.getInteractionHand();
}
}
这里InteractionHand 形参被设置为事件的hand了。
再进行setHand就会生效,而不是没有用处。
2.1.1 注册已有的事件 · FMLTutor (ustc-zzzz.net)(本教程参考了部分)
2.1.2 自定义新的事件 · FMLTutor (ustc-zzzz.net)
事件系统 – Boson 1.16 Modding Tutorial (v2mcdev.com)
10.注册事件 · Minecraft Forge 开发手册 · 看云 (kancloud.cn)
Forge文档
Events – Forge Documentation (minecraftforge.net) (本教程参考了部分)
Mixin教程
分类 : Mixin | 耗子的博客 (mouse0w0.github.io)
事件机制浅谈
浅谈Forge提供的事件机制 – 编程开发 – Minecraft(我的世界)中文论坛 – (mcbbs.net)
该教程版本为1.18.2