用户:MashKJo/1.12.2模组开发教程/4.Forge事件系统:修订间差异

→‎应用:注册注册项:​ // Edit via Wikiplus
 
(未显示同一用户的3个中间版本)
第62行: 第62行:
  }
  }


这里我们新建了一个EventHandler类来存放事件监听器。这里我们监听了EntityJoinWorldEvent——顾名思义,实体在世界上生成时,这个事件会被发布。这个事件有着若干getter,我们通过getEntity这一getter获取了生成在世界中的实体对象,并通过instanceof运算符判断其是否为玩家——没错,玩家加入世界的实质是生成了EntityPlayer对象(注意这里笔者的用词,笔者并没有说“生成了一个EntityPlayer对象”,想想看这是为什么)。然后有鉴于这个方法是static的,我们用了自动注册的注解。
这里我们新建了一个EventHandler类来存放事件监听器。这里我们监听了EntityJoinWorldEvent——顾名思义,实体在世界上生成时,这个事件会被发布。这个事件有着若干getter,我们通过getEntity这一getter获取了生成在世界中的实体对象,并通过instanceof运算符判断其是否为玩家——没错,玩家加入世界的实质是生成了EntityPlayer对象(注意这里作者的用词,作者并没有说“生成了一个EntityPlayer对象”,想想看这是为什么)。然后有鉴于这个方法是static的,我们用了自动注册的注解。


现在运行游戏客户端,随便进入一个存档,你应该能在命令行中看到输出的字符串了。但是注意:这个字符串输出了两次,这实际上是因为两个逻辑端各调用了一次这个监听方法。那么,我们有什么办法能判断当前所处的逻辑端呢?这个会在后面讲到,读者现在留个心眼就行。
现在运行游戏客户端,随便进入一个存档,你应该能在命令行中看到输出的字符串了。但是注意:这个字符串输出了两次,这实际上是因为两个逻辑端各调用了一次这个监听方法。那么,我们有什么办法能判断当前所处的逻辑端呢?这个会在后面讲到,读者现在留个心眼就行。
第72行: 第72行:


=== 事件的优先级 ===
=== 事件的优先级 ===
我们可以为事件监听方法指定被调用的优先级,因为@SubscribeEvent注解中有一个参数priority,类型为EventPriority,是个枚举类,有五个实例:HIGHEST、HIGH、NORMAL、LOW、LOWEST。各自的含义应该不需要笔者多说。priority这一参数默认为NORMAL。
我们可以为事件监听方法指定被调用的优先级,因为@SubscribeEvent注解中有一个参数priority,类型为EventPriority,是个枚举类,有五个实例:HIGHEST、HIGH、NORMAL、LOW、LOWEST。各自的含义应该不需要作者多说。priority这一参数默认为NORMAL。


=== @Cancelable ===
=== @Cancelable ===
第84行: 第84行:
== 应用:注册注册项 ==
== 应用:注册注册项 ==
前面说过,将游戏元素注册进注册表的操作,是基于Forge的事件系统的。实际上,我们该监听这么一个事件:<code>RegistryEvent.Register<T extends IForgeRegistryEntry<T>></code>,然后通过<code>event.getRegistry().register</code>或<code>event.getRegistry().registerAll</code>进行注册。
前面说过,将游戏元素注册进注册表的操作,是基于Forge的事件系统的。实际上,我们该监听这么一个事件:<code>RegistryEvent.Register<T extends IForgeRegistryEntry<T>></code>,然后通过<code>event.getRegistry().register</code>或<code>event.getRegistry().registerAll</code>进行注册。
== 自定义Minecraft事件 ==
说实话,这个在几乎所有情况下都没必要。既然你要自定义Minecraft事件了,那显然是给你自己的游戏元素用的——但是你直接去对应的类中写相应的实现代码不就行了?何必多此一举搞个事件?就算你想让你的模组和其他模组的联动更容易,你也大可暴露一些公开方法即可。
然而,还是说说如何操作吧。
首先你需要根据你的事件发挥的具体作用来判断你的事件类到底该继承什么已有的事件类——直接继承Event类是非常非常罕见的。然后再思考:你的事件都需要有哪些getter和setter?你的事件类的父类又有哪些getter和setter?理清楚你的事件类的构造方法所需要的形参列表。
然后就要到你想发布事件的方法里去new一个你的事件类型了,然后再发布事件:<code>EventBus#post</code>这个方法用于发布某个事件。这个方法是有boolean返回值的,返回true代表事件被取消。所以你一般该这么写:<code>if(!MinecraftForge.EVENT_BUS.post(new MyCustomEvent(...)))</code>。这个if语句后的代码块中,写你期望的默认实现。它的意思即:事件若未被取消,则按照默认实现处理游戏逻辑,同时执行事件监听方法中的追加逻辑。很明显,如果取消了该事件,则可以彻底接管这里的游戏逻辑。
实际上,Forge发布事件的原理就是这样的。但Forge还把发布事件的过程进行了一些包装:对于可取消的事件,if语句中调用的并非post方法,而是位于ForgeHooks和ForgeHooksClient这两个类中的静态方法。这些静态方法先对传入的参数进行了一定的处理,再return post方法的返回值。对于不可取消的事件,post方法一定返回false,那就不会用if了,直接post,相关的静态工具方法的返回值类型也是void而非boolean了。另外有时候ForgeHooks和ForgeHooksClient中的静态方法,还会再把post方法的调用代理给ForgeEventFactory中的静态方法。
另外,很多事件都有子类型:Pre和Post(一般是以静态内部类的形式出现在其父类中)。它们的发布时机通常是这样:Pre发布在默认的游戏逻辑执行前,而Post发布在这之后。一般的规律是:Pre可取消,而Post不可取消——毕竟Post发布的时候,默认的游戏逻辑都执行完了,取消了也没用了,因此就干脆设定为不可取消。所以一般来说,如果我们监听事件只是为了给原版的某些机制附加额外行为,那么监听Pre或Post都没区别;但如果想彻底覆盖原版的游戏逻辑,那么监听Pre并取消是最好的:<code>Event#setCanceled</code>。
行政员、​优秀编辑者、​界面管理员、​监督员、​管理员、​小部件编辑者
3,430

个编辑