跳到主要内容

2-1 插件功能设计的一般方法

从灵感到方案

你的第一个插件没有包含太多的功能,仅仅是在服务器启动的时候将其关闭而已。像这样的功能,无论是理解起来还是通过代码实现,都非常简单。可是,在实际的项目中,用户的需求(或者你自己的灵感)常常是不够清晰的。即使是下面这些规模很小的需求,你或许也很难第一时间想到该如何组织代码:

  • 星门插件:玩家可以使用方块建造星门,并在多个星门之间传送。
  • 登录插件:要求玩家输入密码才能进入主世界。
  • 死亡之箱插件:玩家死亡时在死亡的地方创建一个箱子,包括玩家的物品。

下面我们介绍 Bukkit 的事件系统,并由此向各位读者展示事件驱动(Event Driven)(或称面向事件)的设计方法,它能解决插件开发中绝大多数与 Minecraft 世界交互的需求。

事件和事件驱动编程

程序设计中的事件(Event) 就是指程序所感兴趣的某个情况出现。在游玩 Minecraft 的时候,你肯定遇到过游戏崩溃,启动器会在此时告诉你游戏出现了故障。对于启动器而言,它所关注的事件就是游戏崩溃事件

在 Minecraft 中,事件有很多,例如方块被破坏是方块破坏事件,玩家攻击生物是实体攻击事件实体受击事件,红石粉被激活是方块状态更新事件,等等。这些事件上通常携带着相关的信息,比如方块破坏事件就会记录被破坏的方块位置、破坏来源等。

程序(在这里就是插件)可以选择关注一些事件,术语称作监听(Listen)。当程序监听某事件时,事件的来源就会在合适的时候通知(Notify) 程序。在插件开发中,事件的来源是 Bukkit(和 Paper),或者其它插件,也就是说,只要我们告诉 Bukkit 关注某个事件,Bukkit 就会及时将游戏内最新发生的事情通知给我们

当程序收到事件的通知时,通常做的事情就是执行一小段代码,称作处理(Handle),因此这段代码被称为处理程序(Handler)。通过监听事件和设置处理程序,我们能在游戏内发生某些特定事情时,执行一些相关的操作,也就是当……发生时,就……这么做

这就是大多数插件通过 Bukkit 与游戏交互的方法。

总结来说,插件的工作方式不是上来就执行一长串代码,而是分为以下三步:

  1. 告知 Bukkit(和 Paper)要监听哪些事件。
  2. 等待对应的事件发生。
  3. 获取事件信息并执行相关操作。

虽然我们并没有在第一个插件中提出事件的概念,但其实我们在那时已经在使用事件了,当服务器启动时,就关闭服务器这个功能,就是监听服务器启动事件,并执行功能为关闭服务器的处理程序。

使用事件

了解了事件驱动编程后,我们来看看第二个插件的具体目标。

迎宾广播(Welcome Broadcast)

向服务器内的所有玩家,通报新玩家加入服务器的消息。

为设计简便起见,不需要记忆已经加入过服务器一次的玩家。也就是说,任何玩家加入服务器时都要发送欢迎消息。

功能很简单,但是要如何将它实现为一个插件呢?我们需要将设计的功能转换为事件驱动的模式,也就是把功能改写为当……发生时,就……这么做的形式。这个插件的功能集非常小,只包含一个事件处理程序:

  • 玩家加入服务器事件发生时,就向每个玩家发送消息
术语库

功能集(Feature Set) 就是指程序的所有功能,可以使用功能集的来描述功能数目的多少。功能集的大小往往决定了要设计多少事件处理程序。

在更大的项目中,由功能向事件的转换就不会这么简单了,在今后的项目中我们会见到一些实际的例子,其中不乏有些事件处理器设计得非常微妙,而且难以第一时间想出来。


以上就是设计插件功能的方法,事件驱动编程不仅简单到不可思议,而且更是同样程度的强大。可以说,事件驱动编程涵盖了至少 95% 的插件的至少 95% 的功能。现今,大多数插件都或多或少(更多是毫无保留)地在代码中使用着大量的事件。还记得我们在本书开头提到的Bukkit 协调各个插件有序工作吗?事件驱动编程正是其中的重要一环 —— 每个插件只关注事件本身,而与游戏世界的交互则由 Bukkit 来完成。