欢迎光临散文网 会员登陆 & 注册

MinecraftFORGE事件系统浅谈

2023-07-31 17:39 作者:MegaDarkness  | 我要投稿

前言:读者应该具有一定的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 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 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就会生效,而不是没有用处。

 

 

Forge事件系统浅谈

 Mcmod

该教程版本为1.18.2

MinecraftFORGE事件系统浅谈的评论 (共 条)

分享到微博请遵守国家法律