绪论
Forge的事件系统一直在Forge中占有十分重要的地位,可以这么说,没有事件,就没有Mod。大家可以注意到,主类的preInit
,init
,postInit
方法,全部都是事件驱动的。换句话说,理论上一个Mod的开发教程本身应该从事件讲起。
Forge的事件系统几乎涵盖了方方面面,从服务端到客户端,从世界生成到物品方块行为,从玩家行为到一般实体行为,等等。
Forge的事件系统分为两类,一类是FML生命周期事件,一类是Minecraft事件。
FML生命周期事件
FML生命周期事件,顾名思义,就是FML加载、关闭、和Mod加载等等相关的事件,这些希望监听对应事件的方法使用@EventHandler
注解修饰,并且应在被@Mod
注解修饰的主类下,Forge会寻找并注册仅含一个参数并且参数符合特定类型的方法。如下面三个FML生命周期事件是最常用的:
FMLPreInitializationEvent
FMLInitializationEvent
FMLPostInitializationEvent
这三个事件的使用方法已经讲过,此处不再赘述。
还有下面两个事件:
FMLConstructionEvent
在Mod开始加载时触发。FMLLoadCompleteEvent
在Mod加载完成时触发。
除上面这些之外,还有下面的这些比较常用的用于服务端的FML生命周期事件:
FMLServerAboutToStartEvent
FMLServerStartingEvent
FMLServerStartedEvent
FMLServerStoppingEvent
FMLServerStoppedEvent
想必读者已经可以猜出来这五个事件的异同,并了解这些事件被触发的条件了。
Minecraft事件
Forge本身提供了很多Minecraft事件,这些事件基本上可以完成对Minecraft大部分物品、方块、实体等特性的修改,并且这些事件的数量还在不断地上升。开发者只需要注册一个包含监听这些事件的方法的类,Forge就会挂钩上这些方法。这些方法使用@SubscribeEvent
注解进行修饰,Forge寻找并挂钩这些方法的方式和上面的FML生命周期事件类似,只不过由于挂钩的方式不同,调用的时候效率要更高。
首先我们创造一个类。在包com.github.ustc_zzzz.fmltutor.common
下新建一个文件EventLoader.java
:
src/main/java/com/github/ustc_zzzz/fmltutor/common/EventLoader.java:
package com.github.ustc_zzzz.fmltutor.common;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent;
public class EventLoader
{
public EventLoader()
{
MinecraftForge.EVENT_BUS.register(this);
}
@SubscribeEvent
public void onPlayerItemPickup(PlayerEvent.ItemPickupEvent event)
{
if (event.player.isServerWorld())
{
String info = String.format("%s picks up: %s", event.player.getName(), event.pickedUp.getEntityItem());
ConfigLoader.logger().info(info);
}
}
@SubscribeEvent
public void onPlayerInteract(PlayerInteractEvent event)
{
if (!event.world.isRemote)
{
String info = String.format("%s interacts with: %s", event.entityPlayer.getName(), event.pos);
ConfigLoader.logger().info(info);
}
}
}
这里作者选取了两个事件进行举例,我们一步一步分析上面代码的含义:
src/main/java/com/github/ustc_zzzz/fmltutor/common/EventLoader.java(部分):
@SubscribeEvent
public void onPlayerItemPickup(PlayerEvent.ItemPickupEvent event)
{
if (event.player.isServerWorld())
{
String info = String.format("%s picks up: %s", event.player.getName(), event.pickedUp.getEntityItem());
ConfigLoader.logger().info(info);
}
}
@SubscribeEvent
public void onPlayerInteract(PlayerInteractEvent event)
{
if (!event.world.isRemote)
{
String info = String.format("%s interacts with: %s", event.entityPlayer.getName(), event.pos);
ConfigLoader.logger().info(info);
}
}
@SubscribeEvent
注解的作用是Forge在你注册这个类的时候,会扫描所有具有该注解的方法,然后挂钩。 Forge会根据方法的参数类型来区分不同的事件。比如,这里的onPlayerItemPickup
方法挂钩的就是物品即将被捡起的时候触发的事件PlayerEvent.ItemPickupEvent
,而onPlayerInteract
方法挂钩的就是玩家在和物品或方块互动的时候触发的事件PlayerInteractEvent
。这里因为只是为了演示,我们这里只输出日志信息。
@SubscribeEvent
注解有两个参数,其中一个是receiveCanceled
,与是否取消该事件相关,默认为false
,这个参数不太常用,我们不去管它。还有一个参数是priority
,比较常用,表示事件的优先级,可能的情况有五种:
EventPriority.HIGHEST
EventPriority.HIGH
EventPriority.NORMAL
EventPriority.LOW
EventPriority.LOWEST
默认的优先级是EventPriority.NORMAL
,当然,如果想自定优先级,往往都会选择EventPriority.HIGH
,和EventPriority.HIGHEST
。如果没有特殊需求,这一项还是默认好了。
代码event.player.isServerWorld()
用于检测调用该事件的游戏到底是客户端还是服务端,往往我们只希望服务端调用代码,这是因为服务端产生的变化,客户端往往都会同步,比如这里的向玩家输出游戏控制台信息。代码!event.world.isRemote
也是同样的道理,在后面的内容中,这个用于判断服务端还是客户端的方法很常用。
最后就是事件的注册部分:
src/main/java/com/github/ustc_zzzz/fmltutor/common/EventLoader.java(部分):
public EventLoader()
{
MinecraftForge.EVENT_BUS.register(this);
}
我们使用EventBus
的register
方法,注册了所有我们想要注册的事件。
除此之外,Forge还提供了需要在MinecraftForge.TERRAIN_GEN_BUS
上注册的地形生成事件,需要在MinecraftForge.ORE_GEN_BUS
上注册的矿物生成事件等等。
最后在CommonProxy
中注册:
src/main/java/com/github/ustc_zzzz/fmltutor/common/CommonProxy.java(部分):
public void init(FMLInitializationEvent event)
{
new CraftingLoader();
new EventLoader();
}
打开游戏试试吧~
Event类解析
Forge提供的所有事件,都是net.minecraftforge.fml.common.eventhandler.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)
设置该事件的优先级,上面已有说明。