绪论

Forge的事件系统一直在Forge中占有十分重要的地位,可以这么说,没有事件,就没有Mod。大家可以注意到,主类的preInitinitpostInit方法,全部都是事件驱动的。换句话说,理论上一个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);
    }

我们使用EventBusregister方法,注册了所有我们想要注册的事件。

除此之外,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类的子类。当前Forge版本下这个类的所有的子类的用法作者在附录中列出了一张表,以供读者参考。这一部分针对Event类本身。

Event类添加了下面几个公开方法:

  • public boolean isCancelable()
    返回该事件是否可以被取消。
  • public boolean isCanceled()
    返回该事件是否已被取消。
  • public void setCanceled(boolean cancel)
    设置该事件是否被取消。
  • public boolean hasResult()
    返回该事件是否有结果,添加了@HasResult注解的事件默认为true,否则为false
  • public Result getResult()
    返回该事件的结果,有Result.DENYResult.DEFAULTResult.ALLOW三种,默认为Result.DEFAULT
  • public void setResult(Result value)
    为该事件设置一个结果。
  • public ListenerList getListenerList()
    获取所有注册该事件的监听器。
  • public EventPriority getPhase()
    获取该事件的优先级,上面已有说明。
  • public void setPhase(EventPriority value)
    设置该事件的优先级,上面已有说明。

results matching ""

    No results matching ""