3,417
个编辑
MCBBS Wiki欢迎您共同参与编辑!在参与编辑之前请先阅读Wiki方针。
如果在编辑的过程中遇到了什么问题,可以去讨论板提问。
为了您能够无阻碍地参与编辑 未验证/绑定过邮箱的用户,请尽快绑定/验证。
MCBBS Wiki GitHub群组已上线!
您可以在回声洞中发表吐槽!
服务器状态监控。点击进入
本站由MCBBS用户自行搭建,与MCBBS及东银河系漫游指南(北京)科技有限公司没有从属关系。点此了解 MCBBS Wiki 不是什么>>
(创建页面,内容为“事件系统,是Forge的一大特色——没错,Minecraft原版并没有事件这一概念。 在上一节,我们已经看到了事件系统的应用——就是那三个被<code>@EventHandler</code>注解的监听器方法。读者可能已经注意到了:这三个方法的形参都是唯一的,实际上,这是必须的,且它们各自的形参类型就代表了监听器监听的事件类型。 FMLPreInitializationEvent、FMLInitializationEven…”) |
(// Edit via Wikiplus) |
||
| 第6行: | 第6行: | ||
== 事件系统存在的意义 == | == 事件系统存在的意义 == | ||
作为Modder,肯定有修改原版的游戏逻辑的需求,但是原版的很多东西都是写死的,如果不借助黑魔法之类的东西,根本没法直接修改原版的源码。这里的黑魔法指的是什么?如果是高级一点的,那就用Mixin——一种能让Modder在Minecraft源码中插入自己的代码的工具,被Fabric尤为推崇;如果再原始一点,那就是手搓ASM……至于反射,不提也罢。 | |||
上面提到的这些黑魔法,比较难用,而且很容易把游戏搞崩。于是Forge出手了,通过监听Minecraft事件,Modder们可以轻松地改变原版的很多游戏逻辑。原理是什么?其实就是Forge帮Modder们把黑魔法的步骤做完了,它在Minecraft原版代码中插入了很多代码,一般流程是:在Minecraft相关游戏逻辑执行前后,Forge发布某个特定类型的Minecraft事件,再根据这个事件的监听情况决定原版逻辑到底会不会被执行,如果不会,到底要怎么改变。很明显,Forge帮模组开发者们统一执行黑魔法步骤,而非让模组开发者们各自使用黑魔法,大大提高了Forge Mod之间的兼容性,保证了良好的跨Mod交互。这就是Minecraft事件存在的意义:让Modder们能轻松干涉原版机制,且同时保证Mod之间良好的交互。 | |||
至于Forge事件?这个也有保证良好的跨Mod交互的功能,此外还有为Forge的一些机制服务的功能,这些用的很少,因此不是本节的重点内容。 | |||
== 事件监听方法的格式 == | == 事件监听方法的格式 == | ||
对于Forge事件的监听方法格式,上一节已有示例。 | |||
而对于Minecraft事件的监听方法,有以下几点要求: | |||
* 访问修饰符必须为public | |||
* 返回值类型必须为void | |||
* 形参必须唯一,且形参类型必须为Event类的子类——Event类实际上是所有Minecraft事件的共同父类 | |||
* 必须打上@SubscribeEvent注解 | |||
比如,举个例子: | |||
@SubscribeEvent | |||
public void exampleEventHandler(ExampleEvent event) | |||
{ | |||
... | |||
} | |||
通过这个event,你通常会有一堆getter和setter能用,由此你就能干涉原版的机制。 | |||
另外注意到,这个方法是非静态的,但其实,是静态的也无妨,只是注册的时候会略有点小区别,读者往下看即可。 | |||
== Minecraft事件的注册、取消和优先级 == | |||
=== 事件总线 === | |||
这是一个解释起来非常抽象的概念,读者可以简单地把它理解为一个“中转站”——所有事件被发布后,会被传到事件总线中处理,而事件总线就会尝试去获取所有事件监听方法,并产生实际效果。 | |||
Forge内建了三条事件总线(EventBus):一般事件总线(EVENT_BUS)、矿物生成总线(ORE_GEN_BUS)和地形生成总线(TERRAIN_GEN_BUS),它们在<code>net.minecraftforge.common.MinecraftForge</code>类中以静态字段的形式存在。看看它们的名字,你就知道它们各自的用途都是什么。你当然也可以new一个自己的事件总线,但是没必要。 | |||
=== 事件的注册 === | |||
很简单,用<code>EventBus#register</code>这一方法就好了。注意它要求传入一个Object——这有点让人摸不着头脑了,不是吗? | |||
事情是这样的:传入什么东西,取决于你的事件监听方法是不是静态的。如果是静态的,那么传入该方法所在的类对应的Class<?>对象;如果是非静态的,则直接new一个该类的对象,传入进去。 | |||
不过,对于static的情况,实际上,还有一种便捷的注册方法:直接给监听方法所在的类打上<code>@Mod.EventBusSubscriber</code>这个注解,这个注解可以传入你的modid,以及应该发挥作用的物理端。 | |||
==== 实例 ==== | |||
<code>src/main/java/net/tutorial_mod/event/EventHandler.java:</code> | |||
package net.tutorial_mod.event; | |||
import net.minecraft.entity.player.EntityPlayer; | |||
import net.minecraftforge.event.entity.EntityJoinWorldEvent; | |||
import net.minecraftforge.fml.common.Mod; | |||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; | |||
import net.tutorial_mod.TutorialMod; | |||
@Mod.EventBusSubscriber(modid = TutorialMod.MODID) | |||
public class EventHandler { | |||
@SubscribeEvent | |||
public static void testEventHandler(EntityJoinWorldEvent event){ | |||
if(event.getEntity() instanceof EntityPlayer) System.out.println("My event handler works!"); | |||
} | |||
} | |||
这里我们新建了一个EventHandler类来存放事件监听器。这里我们监听了EntityJoinWorldEvent——顾名思义,实体在世界上生成时,这个事件会被发布。这个事件有着若干getter,我们通过getEntity这一getter获取了生成在世界中的实体对象,并通过instanceof运算符判断其是否为玩家——没错,玩家加入世界的实质是生成了EntityPlayer对象(注意这里笔者的用词,笔者并没有说“生成了一个EntityPlayer对象”,想想看这是为什么)。然后有鉴于这个方法是static的,我们用了自动注册的注解。 | |||
现在运行游戏客户端,随便进入一个存档,你应该能在命令行中看到输出的字符串了。但是注意:这个字符串输出了两次,这实际上是因为两个逻辑端各调用了一次这个监听方法。那么,我们有什么办法能判断当前所处的逻辑端呢?这个会在后面讲到,读者现在留个心眼就行。 | |||
=== 如何知道自己要用什么事件? === | |||
所有Minecraft事件都是Event类的子类,因此理论上你只需要去看看Event的继承树就好。但实际上这么做并不妥,因为Forge的事件类型非常多,你这么搞,效率低下且不说,而且Forge的有些事件的名字起得并不好,你不一定看到名字就知道某个事件是在什么时候发布的、是用来干什么的。因此,最好的方法是:先思考你要干涉什么原版机制,再找到原版相关的类,看看其中有没有Forge插入进去的代码,如果有,看看Forge在这里面发布了什么事件即可。另外说一句,Forge插入原版类的代码中,Forge的类一般都会写全包名而非在源文件开头import,因此会显得很突兀;且Forge的代码一般都有详尽的注释。所以,有没有Forge插进去的代码,一看即知。 | |||
那么,如果真的找不到能用的事件,怎么办?尽管Forge的事件非常多,有的甚至做到了仅仅针对某个单一的游戏元素(如RenderItemInFrameEvent,这个事件影响物品在展示框中的渲染情况),但任何API都做不到完全满足使用者的需求。Forge官方早在多年前就停止了对1.12 Forge的维护,所以指望着去Pull Request是不行了。这个时候,你就只能考虑使用一些黑魔法了。 | |||