MinecraftFORGE事件系統(tǒng)淺談
前言:讀者應(yīng)該具有一定的Java基礎(chǔ)
Forge事件系統(tǒng)
MC原版并沒有事件這種東西,事件系統(tǒng)是Forge提供的修改添減原版內(nèi)容,特性的東西。
Forge的事件系統(tǒng)覆蓋面極廣,模組注冊,世界生成,玩家行為,渲染等。
例如:當玩家刻更新時的事件(TickEvent.PlayerTickEvent)。
絕大多數(shù)的事件都是在主事件總線上觸發(fā)(MinecraftForge.EVENT_BUS)。
使用
將事件訂閱到事件總線上。
EventBus提供的shutdown()方法是來關(guān)閉總線的。
創(chuàng)建Event Handler
事件方法是void類型的,不返回結(jié)果。該方法可以是靜態(tài)(static void)或?qū)嵗?void)的。
注冊方法:
?
值得一提的是,泛型事件處理程序也需要指定泛型的類。必須在 main mod 類的構(gòu)造函數(shù)中注冊事件處理程序。
?
實例事件
public?class?MyForgeEventHandler??{
????@SubscribeEvent
????public?void?pickupItem(EntityItemPickupEvent?event)?{
????????System.out.println("Item?picked?up!");
????}
}
我們先從實例的方法開始說起。可以看到這里最為特殊的就是@SubscribeEvent注解,它用來標記這是一個訂閱器,至于它具體監(jiān)聽的事件是由它的參數(shù)類型決定的,在這里它的參數(shù)類型是EntityItemPickupEvent,說明它監(jiān)聽的是實體撿起物品這個事件。
當然,對于實例方式的事件處理這樣還不夠,我們還得手動在某個地方實例化它并把它注入到事件總線里,我們之前說過Minecraft里有兩條事件總線「Forge總線」和「Mod總線」,Mod總線主要負責游戲的生命周期事件,也就是初始化過程的事件,而Forge總線負責的就是除了生命周期事件外的所有事件。你可以用MinecraftForge.EVENT_BUS.register()方法將你的事件實例注冊到Forge總線中,也可用FMLJavaModLoadingContext.get().getModEventBus().register()方法將其注冊到Mod總線中,一般情況下你應(yīng)該在你的Mod主類的初始化方法里注冊這些事件。
在我們的例子里就是如下:
MinecraftForge.EVENT_BUS.register(new?MyForgeEventHandler());
靜態(tài)事件
當然所有的事件處理器都要手動注冊非常的麻煩,F(xiàn)orge同樣提供了一個靜態(tài)的注冊事件的方法。內(nèi)容如下:
?@Mod.EventBusSubscriber
public?class?MyStaticClientOnlyEventHandler?{
????@SubscribeEvent
????public?static?void?drawLast(RenderLevelLastEvent?event)?{
????????System.out.println("Drawing!");
????}
}
可以看到,這里與實例注冊不同的是增加了@Mod.EventBusSubscriber,他會自動注冊類下帶有 @SubscribeEvent注解的靜態(tài)方法。
你可以用@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)來指定你要注入到Mod總線中。當然這里的參數(shù)不止一個,大家可以自行查看@Mod.EventBusSubscriber的具體內(nèi)容。
更為詳細的使用事件方法請看?早上?的Forge事件機制淺談。
子事件
例如:TickEvent下有PlayerTickEvent,ClientTickEvent等
許多事件本身有不同的變體。這些可以是不同的,但都基于一個共同的因素(例如),或者可以是具有多個階段的事件(例如)。請注意,如果偵聽父事件類,則將收到對所有子類的方法的調(diào)用。?
模組事件總線
mod 事件總線主要用于偵聽 mods 應(yīng)初始化的生命周期事件。mod 總線上的每個事件都需要實現(xiàn) 。其中許多事件也是并行運行的,因此可以同時初始化模組。這確實意味著您無法在這些事件中直接執(zhí)行來自其他模組的代碼。為此使用系統(tǒng)。IModBusEvent InterModComms
?
以下是在 mod 事件總線上的 mod 初始化期間調(diào)用的四個最常用的生命周期事件:
FMLCommonSetupEvent
FMLClientSetupEvent & FMLDedicatedServerSetupEvent
InterModEnqueueEvent
InterModProcessEvent
注意
????和 僅在其各自的分發(fā)上調(diào)用。FMLClientSetupEvent?FMLDedicatedServerSetupEvent。
????這幾個生命周期事件都是并行運行的,因為它們都是 的子類。如果要在任何 期間在主線程上運行運行代碼,可以使用 來執(zhí)行此操作。??????ParallelDispatchEvent ParallelDispatchEvent#enqueueWork
?
在生命周期事件旁邊,有一些雜項事件在 mod 事件總線上觸發(fā),您可以在其中注冊、設(shè)置或初始化各種內(nèi)容。與生命周期事件相比,這些事件中的大多數(shù)不是并行運行的。舉個典型例子:RegisterEvent
這里有一個很好的經(jīng)驗法則:當事件應(yīng)該在 mod 初始化期間處理時,會在 mod 事件總線上觸發(fā)事件。
?
Event類解析
????Forge提供的所有事件,都是Event類的子類。當前Forge版本下這個類的所有的子類的用法zzzz大佬在他的教程附錄中列出了一張表,以供讀者參考。這一部分針對Event類本身。
Event類添加了下面幾個公開方法:
·?public boolean isCancelable()
返回該事件是否可以被取消。
·?public boolean isCanceled()
返回該事件是否已被取消。
·?public void setCanceled(boolean cancel)
設(shè)置該事件是否被取消。
·?public boolean hasResult()
返回該事件是否有結(jié)果,添加了@HasResult注解的事件默認為true,否則為false。
·?public Result getResult()
返回該事件的結(jié)果,有Result.DENY,Result.DEFAULT,Result.ALLOW三種,默認為Result.DEFAULT。
·?public void setResult(Result value)
為該事件設(shè)置一個結(jié)果。
·?public ListenerList getListenerList()
獲取所有注冊該事件的監(jiān)聽器。
·?public EventPriority getPhase()
獲取該事件的優(yōu)先級,上面已有說明。
·?public void setPhase(EventPriority value)
設(shè)置該事件的優(yōu)先級,上面已有說明。
(以上大部分來自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;
????}
}
接下來我們需要在指定位置發(fā)布事件(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
????????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);
}
這個事件的意思就是,當你右鍵一個劍類物品,就會清除它。
右鍵后 可以看到事件成功運行,物品被清除。
創(chuàng)建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
????????UseItemEvent?event?=?new?UseItemEvent(p_105236_,?p_105236_.getItemInHand(p_105238_),?p_105238_,?p_105237_);
????????MinecraftForge.EVENT_BUS.post(event);
????????//設(shè)置
????????p_105238_?=?event.getInteractionHand();
????}
}
這里InteractionHand 形參被設(shè)置為事件的hand了。
再進行setHand就會生效,而不是沒有用處。
?
?
Forge事件系統(tǒng)淺談
?Mcmod
該教程版本為1.18.2