设计
插件系统
插件系统Or模块?
首先插件系统的意义在于模块化功能,使用多模块构建项目时,团队协作上不同的人维护的项目会有自己的branch,难免遇到branch不同以及对不同模块的引用和修改。不同插件的维护相较于多模块的团队协作来说是便利的,也是更方便的工作流 ——引用自BrokenDust
核心的定位
Minestom不提供任何原版特性,基于Minestom开发的核心应当拥有其在品类上的定位,如小游戏服专用、RPG服专用、生存专用等等。
不同品类定位的核心应当提供该品类的基础框架,比如小游戏服应当提供小游戏实例、房间等功能,RPG服应当提供属性、技能、怪物、NPC等功能
这些功能是构建该品类游戏必备的基石,决定整个服务器运转的基本逻辑,应当在事件节点树上拥有清晰的位置,并且被所有插件了解
插件系统的定位
插件系统的定位是在核心所定义的基础框架之下拓展或修改游戏机制,而不是将核心作为一个连框架都没有的空壳,所有内容统统由插件实现
Minestom核心与其插件的关系,应当类比于Bukkit与其插件的关系,minestom核心提供核心玩法,类比于Bukkit提供原版特性,而插件只提供拓展机制
举例
紫马不露尝试开发一款RPG核心,他认为核心应当只提供插件加载逻辑,属性、技能等内容完全交由插件实现
然而在他开发属性插件时遇到一个问题,他想要实现类似Bukkit中的血量缩放效果,即将任意生命值所表示的红心压缩为两行,避免血量过高出现红心占满屏幕的问题。在Bukkit中,这个机制由Bukkit核心提供,插件只需要调用API即可实现。
想要在Minestom中实现,紫马不露首先想到可以拦截血量更新的数据包并修改,然而Minestom出于性能原因,不提供修改数据包的API,因此他不得不取消原数据包的发送,创建其副本再发送。可是数据包不仅不可变,还不可继承,不能添加额外的标记,也不能在不触发事件的情况下发送数据包,因此无法识别哪个数据包是已经修改后的,在监听事件时就会造成无限递归问题。
紫马不露前往Discord寻求帮助,得到的回答是三个选择:
- 自己维护一套生命值系统。可是这会导致其他操作生命值的插件无法与其兼容,破坏了插件的模块化
- 继承Player类,重写原生的生命值系统,并注册到PlayerProvider。可是PlayerProvider是唯一的,无法与其他插件兼容,破坏了插件的模块化
- 通过调用仅供内部使用的API,在不触发事件的情况下发送数据包。可是内部API并不稳定,随时有可能更改,破坏了插件的稳定性
这时,紫马不露终于意识到自己的问题,插件系统的定位并不在于将所有内容交由插件实现,而是应当在核心中提供基础框架,插件只负责拓展和修改。如果他在一开始就把属性功能放在核心中,那么血量缩放功能就可以作为属性功能的一部分,由核心提供API供插件调用,而不是让插件去实现一个与核心或其他插件冲突的属性系统。