事件

是什么

你关注了一个微信公众号,微信公众号发文章时会通知你有新文章了,你阅读了新文章后可能会做某些操作比如评论,点赞等

在上面场景中,微信公众号发布新文章是一个事件,关注微信公众号就是订阅了这个事件,通知你时代表调用了你的一个函数来通知,新文章代表的是在这个事件包含的内容(参数),做出的某些操作就是你在这个函数中的操作,场景可以简化为:

  1. 关注公众号
  2. 公众号发布新文章
  3. 通知你
  4. 做出操作

如果替换成事件,则为:

  1. 订阅事件
  2. 事件发生
  3. 调用你在订阅时给出的函数
  4. 你在函数内做出操作

详细可以参考观察者模式

Forge中的事件

大概分为两条线,一个是游戏中的事件线,一个是模组事件线

游戏事件线

如攻击实体,与实体交互等事件

模组事件线

点我

怎么用

调用的函数定义方法

在函数前加上注解 @SubscribeEvent,参数名为你要订阅的事件,如我想要订阅TickEvent.PlayerTickEvent则函数为:

1
2
3
4
@SubscribeEvent
public void onTick(TickEvent.PlayerTickEvent playerTickEvent) {
//do something...
}

如何注册函数

有两种方法来注册你的函数

第一种方法

对于游戏事件线,使用MinecraftForge.EVENT_BUS.register({Class})来将这个类注册进事件系统,其中的Class可以为静态也可以new一个,如果是静态的则需要将你要传递的函数设为static,不过为new,则将static去除,下面给出了两个例子

1
2
3
4
5
6
7
8
9
10
11
12
public class SubsucribeExample{

public SubsucribeExample(){
MinecraftForge.EVENT_BUS.register(this);
}

@SubscribeEvent
public void onTick(TickEvent.PlayerTickEvent playerTickEvent) {
//do something...
}

}

java

1
2
3
4
5
6
7
8
9
10
11
12
public class SubsucribeExample{

public SubsucribeExample(){
MinecraftForge.EVENT_BUS.register(SubsucribeExample.class);
}

@SubscribeEvent
public static void onTick(TickEvent.PlayerTickEvent playerTickEvent) {
//do something...
}

}

对于模组事件

1
FMLJavaModLoadingContext.get().getModEventBus().addListener({函数名});

即可注册函数,不用在函数前加上@SubscribeEvent,或

1
FMLJavaModLoadingContext.get().getModEventBus().register({类名});

同样也要注意静态问题

第二种方法

在整个类前加入@Mod.EventBusSubscriber注解,注解源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface EventBusSubscriber {
Dist[] value() default { Dist.CLIENT, Dist.DEDICATED_SERVER };

String modid() default "";

Bus bus() default Bus.FORGE;

enum Bus {
FORGE(Bindings.getForgeBus()),
MOD(()-> FMLJavaModLoadingContext.get().getModEventBus());

private final Supplier<IEventBus> busSupplier;

Bus(final Supplier<IEventBus> eventBusSupplier) {
this.busSupplier = eventBusSupplier;
}

public Supplier<IEventBus> bus() {
return busSupplier;
}
}
}

阅读源码可知,value的值为物理服务端还是物理客户端,或者两者均可,modid为你的模组id,bus为你要订阅的事件线,FORGE为游戏事件线,MOD为模组事件线,通过配置这三者可以达到你想要的目的

需要注意的是,通过这种方法注册,函数需要都是static

事件的一些属性

能否取消

通过调用event中的isCancelable()函数来得到当前事件是否能被取消,或者阅读源码查看是否有@Cancelable注解,有即为可以取消,函数返回true,否即没有,如果需要取消事件,可以调用setCanceled(boolean canceled)来决定是否取消事件,不能取消一个不可取消的事件,否则会崩溃

事件结果

有一些事件有结果,比如树苗长为大树事件,官方注解中提到

image-20220218184541813

通过调用setResult(Result result)来返回一个结果,可以调用hasResult()来判断该事件是否有结果,有些没有有些有

优先级

@SubscribeEvent的注解具有优先级,可以通过设置priority值来设置处理优先级,可以为

  • HIGHEST
  • HIGH
  • NORMAL
  • LOW
  • LOWEST

从上到下代表优先级从高到底,优先级高的会先执行(默认NORMAL)

子事件

如果你订阅了一个父事件,比如PlayerEvent,则他属下的所有事件发生时都会触发你的函数

注册

在之前提到了游戏需要添加物品,方块等,在这里称为注册物品,需要注册物品才能让游戏知道有这个物品。

根据什么注册

注册物品通常用使用的是ResourceLocation来注册,在实例化一个物品时通过setRegistryName来设置ResourceLocation,也可以实例化后再自己设置,注意的是只能设置一次,使用getRegistryName来获取。

如果一个Block和一个Item的ResourceLocation内名字相同,不会冲突,但如果不同的Item使用了同一个namespace和path,则最后注册的将会覆盖第一个

如何注册

DeferredRegister

官方文档给出的案例:

1
2
3
4
5
6
7
8
private static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID);

public static final RegistryObject<Block> ROCK_BLOCK = BLOCKS.register("rock", () -> new Block(BlockBehaviour.Properties.of(Material.STONE)));

public ExampleMod() {
BLOCKS.register(FMLJavaModLoadingContext.get().getModEventBus());
}

Register事件

通过订阅Register事件来注册物品,如果是注册Block,则为

1
2
3
4
@SubscribeEvent
public void registerBlocks(RegistryEvent.Register<Block> event) {
event.getRegistry().registerAll(new Block(...), new Block(...), ...);
}

如果是Item,则为

1
2
3
4
@SubscribeEvent
public void registerItems(RegistryEvent.Register<Item> event) {
event.getRegistry().registerAll(new Item(...), new Item(...), ...);
}

剩下待补充…

怎么引用

第一种方法为使用RegistryObject,第二种方法为@ObjectHolder,这些在注册完物品后会更新

RegistryObject

官方的例子比较好地说明了如何使用

1
2
3
4
5
public static final RegistryObject<Item> BOW = RegistryObject.of(new ResourceLocation("minecraft:bow"), ForgeRegistries.ITEMS);

// assume that ManaType is a valid registry, and 'neomagicae:coffeinum' is a valid object within that registry
public static final RegistryObject<ManaType> COFFEINUM = RegistryObject.of(new ResourceLocation("neomagicae", "coffeinum"), () -> ManaType.class);

java

使用RegistryObject.of(ResourceLocation,游戏中的某个东西)

@ObjectHandler

官方的规则较为复杂..在这只放出一些使用案例,由于后面注释过长,因此想要看原来的点这,在此只会说明注入的namespace和路径,如果没有诸如成功,则会注释其他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@ObjectHolder("minecraft") // namespace: minecraft
class AnnotatedHolder {
public static final Block diamond_block = null; // minecraft:diamond_block
@ObjectHolder("ambient.cave")
public static SoundEvent ambient_sound = null; // minecraft:ambient.cave

// Assume for the next entry that [ManaType] is a valid registry.
@ObjectHolder("neomagicae:coffeinum")
public static final ManaType coffeinum = null; // neomagicae:coffeinum

public static final Item ENDER_PEARL = null; // minecraft:ender_pearl

@ObjectHolder("minecraft:arrow")
public static final ArrowItem arrow = null; // minecraft:arrow

public static Block bedrock = null; // 没有final,获得不成功

public static final CreativeModeTab group = null; // 没有对应的Registry,所以会抛出异常
}

class UnannotatedHolder { // 注意到没有注解
@ObjectHolder("minecraft:flame")
public static final Enchantment flame = null; // minecraft:flame

public static final Biome ice_flat = null; // 没获得

@ObjectHolder("minecraft:creeper")
public static Entity creeper = null; // Entity没有对应的Registry,会抛出异常

@ObjectHolder("levitation")
public static final Potion levitation = null; // 没有namespace,抛出异常,没获得
}

具体有没有对应的Registry参考 net.minecraftforge.registries.ForgeRegistries。也可以自定义Registry,如上面代码中的ManaType

创建自己的Registry

要创建自己的Registry,首先要实现IForgeRegistryEntry,其中定义了setRegistryName和getRegistryName,官方推荐继承ForgeRegistryEntry,其中有默认的实现

将自己的Registry注册到游戏中

首先需要一个类继承了ForgeRegistryEntry或者实现了IForgeRegistryEntry

订阅RegistryEvent.NewRegistry,经过测试,这一行代码能顺利注册自己的Registry

1
IForgeRegistry<ManaType> manaTypes = new RegistryBuilder<ManaType>().setName(new ResourceLocation("examplemod:mana_type")).setType(ManaType.class).create();

其中的manaTypes变量最好保存,他只是做个例子,你可以自定义自己的registry,比如技能等,setName和setType是必须的,还有其他的函数诸如setMaxID等。实测不加入setName可以注册成功,可以进入我的世界那个界面,但进入世界时会崩溃,所以需要setName

处理不存在的registry object

当mod更新时,可能会删除或者加入一些物品,对于那些删除的物品,可以通过订阅RegistryEvent.MissingMappings来解决删除物品的问题,具体见

模组生命周期

模组加载过程中,各种生命周期事件会在特定的事件总线(FMLJavaModLoadingContext.get().getModEventBus())上触发,比如注册对象,准备数据等

注册表事件

在mod被实例化后会被触发,有两个事件,NewRegistry和Register事件,NewRegistry即为创建自定义Registry,Register使用方法之前提到了,例子:

1
2
3
4
@SubscribeEvent
public void registerItems(RegistryEvent.Register<Item> event) {
event.getRegistry().registerAll(new Item(...), new Item(...), ...);
}

数据生成

待补充

一般设置

事件为FMLCommonSetupEvent,在物理服务端和物理客户端中都会被触发,比如注册capabilities(后期会提到)

不同端的设置

对于物理客户端,事件为FMLClientSetupEvent,对于物理服务端,事件为FMLDedicatedServerSetupEvent

跨模组交流

两个事件:

  • InterModEnqueueEvent
  • InterModProcessEvent

第一个事件为向不同mod发送消息,第二个事件为接受不同消息,发送和接受参考InterModComms类,包为net.minecraftforge.fml

其他

还有两个生命周期事件

  • FMLConstructModEvent,在mod实例化之后,RegistryEvent之前触发
  • FMLLoadCompleteEvent,在InterModComms(跨模组交流)后触发