前言:读者应该具有一定的Java基础

Forge事件系统

MC原版并没有事件这种东西,事件系统是Forge提供的修改添减原版内容,特性的东西。

Forge的事件系统覆盖面极广,模组注册,世界生成,玩家行为,渲染等。

例如:当玩家刻更新时的事件(TickEvent.PlayerTickEvent)。

绝大多数的事件都是在主事件总线上触发(MinecraftForge.EVENT_BUS)。

使用

将事件订阅到事件总线上。

EventBus提供的shutdown()方法是来关闭总线的。

创建Event Handler

事件方法是void类型的,不返回结果。该方法可以是静态(static void)或实例(void)的。

注册方法:

 

浅谈Forge的事件系统和使用-第1张图片

值得一提的是,泛型事件处理程序也需要指定泛型的类。必须在 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);
}

这个事件的意思就是,当你右键一个剑类物品,就会清除它。

浅谈Forge的事件系统和使用-第2张图片浅谈Forge的事件系统和使用-第3张图片

可以看到事件成功运行,物品被清除。

创建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