Bukkit开发教程/入门指南
我们假设你是什么都不会的新手,手把手教你入门
完全编程新手的 Bukkit 插件开发入门教程,按照以下步骤操作,编译你的第一个插件吧!
只教开发,不教开服,本教程需要有开服技术基础,最起码要会编辑配置文件,对服务端目录熟悉。
# Bukkit 是什么?
Bukkit (CraftBukkit) 是很早之前出现的一套 Minecraft Java Edition 服务端插件标准。现在几乎已经没有人使用纯血 Bukkit 了,Bukkit 已经成为了 SpigotMC 项目的一部分。现在我们更多使用的是 Spigot、Paper 等 Bukkit 衍生接口,我们依然叫它 Bukkit 插件只是因为它依然位于底层,并且我们已经习惯这么叫它了。
# 准备环境
先安装以下环境所需软件
- (用于测试) Minecraft 客户端 (这里使用 1.20.4 来作为示例)
- (用于测试) Spigot (opens new window) 或 Paper (opens new window) 服务端预构建版本
- JDK (按你的游戏版本决定)
- 1.16 及以前使用
Java 8
- 1.17 到 1.20.4 用
Java 17
- 1.20.5 及以上用
Java 21
- 1.16 及以前使用
- IntellIJ IDEA Community Edition (opens new window)
安装并打开 IDEA 后,在主页点击 Plugins
,在 Marketplace
搜索 中文语言包
,安装第一个,安装完成后点击 Restart IDE
。
然后参考这篇帖子 (opens new window)的教程,解决之后可能会出现的 IDEA 控制台乱码。
# 新建项目
点击新建项目,如图所示
org.example
是默认的示例组ID,仅用于教学,请勿用于实际,否则早晚出问题。
所谓「组ID」是用来辨别程序作者的,每个作者都必须要有一个唯一的组ID。为了唯一性,我们约定以下规则:
- 如果你有域名,比如我的域名是
mrxiaom.top
。那么将域名按.
分隔,倒过来写就是组ID了。比如top.mrxiaom
。 - 如果你没有域名,可以用邮箱,如果用邮箱,建议加个前缀
email
。比如我的邮箱是mrxiaom@qq.com
,一样是倒过来写,那么组ID就是com.qq.mrxiaom
或者email.com.qq.mrxiaom
。 - 如果你有 Github 账号,可以使用
io.github.自己的全小写用户名
作为组ID,比如io.github.mrxiaom
,Gitee 同理,io.gitee.mrxiaom
。并不是所有网站都可以这么做,可以用
io.github
和io.gitee
是因为,这两家开源平台都有向用户提供网页托管服务,你只要有账号,xxx.github.io
之类的二级域名就是归你的,那么就可以适用于「你有域名」这种情况。 - (不推荐) 如果上面几个你都不想用,那么请自由发挥,起得尽可能特殊,没人会跟你争就行。比如
kira.uwu.plugin
。
如果你的组ID跟别人的组ID冲突,两个人的插件加到同一个服务器的时候可能会出问题。所以,一定要起一个能够确保唯一性的组ID。
如果你未来想将代码开源并发布到 Maven 中心仓库,请尽可能地使用域名作为组ID。
# 配置依赖
创建项目后,将来到这个界面
先打开 build.gradle.kts
待命,点击左侧的「构建」标签页,等待同步完成
然后在 repositories
下面添加开发所需仓库,添加完成后代码如下
repositories {
mavenCentral()
maven("https://repo.codemc.io/repository/maven-public/")
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
maven("https://repo.rosewooddev.io/repository/public/")
}
2
3
4
5
6
- codemc 提供一部分依赖插件
- spigotmc 提供 spigot 接口
- rosewooddev 提供 NMS 依赖,在之后的章节中将会详细说明 NMS 的用途。
添加完成后,我们开始添加依赖。
在 dependencies
下面添加开发所需依赖,添加完成后代码如下
dependencies {
compileOnly("org.spigotmc:spigot-api:1.20.4-R0.1-SNAPSHOT")
}
2
3
1.20.4
需要改成你自己的服务端版本。后面的-R0.1
是固定的,代表 Bukkit 版本 (在MC高版本中,这个版本号从不更新)。后面的-SNAPSHOT
是快照版本固定格式。
原本dependencies
里面那两句testImplementation
可以删除,用途不大。
点击「构建」->「同步」下面的「重新加载Gradle项目」
(为了方便,之后我会简单地用刷新项目指代这个操作),等待依赖下载完成。
# 新建主类
在「项目」下面依次展开 src
、main
,在 java
文件夹上右键,点击「新建」->「软件包」
在弹出的输入框填入包名。包名由 组ID 和 软件名(全小写) 组成,用 .
连接。
这里使用 org.example.exampleplugin
,请改成你自己的包名。
紧接着,我们在刚刚新建的软件包里面新建一个主类。
先右键,点击「新建」->「Java类」,在弹出的输入框填入类名,主类名一般是插件名或者 Main、PluginMain、BukkitMain 等等,看个人喜好。
这里使用插件名 ExamplePlugin
作为主类名,新建好后如下所示。
在左花括号{
前面加 extends JavaPlugin
,将鼠标悬停到 红色字(报错) 上面,点击导入类
。
然后在花括号之间输入以下代码
@Override
public void onEnable() {
getLogger().info("你好,Minecraft!");
}
2
3
4
如下所示
# 添加插件信息
接下来我们要让服务端能够识别并加载这个插件。依然是在「项目」下面依次展开 src
、main
,在 resources
文件夹上右键,点击「新建」->「文件」,输入文件名称 plugin.yml
,在新建的文件中输入以下代码(自行替换代码里面的中文为相关信息)
name: 插件名
version: 版本号
main: 主类路径,先空着,后面会说怎么填
authors: [ 作者名字 ]
api-version: 插件接口版本,只要填到大版本就行了,最低填写1.13,要兼容1.13以上的服务端必须要填写这一项
2
3
4
5
如下所示
主类路径可以通过右键主类,点击「复制路径/引用…」->「复制引用」来获取。
# 配置替换版本号
如果你想要 build.gradle.kts
里的 version 跟 plugin.yml
里的 version 保持一致,那么请先将 plugin.yml
里 version 的值改成 ${version}
,如下所示
version: ${version}
然后在 build.gradle.kts
末尾添加以下代码即可
tasks {
processResources {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
from(sourceSets.main.get().resources.srcDirs) {
expand(mapOf("version" to project.version))
include("plugin.yml")
}
}
}
2
3
4
5
6
7
8
9
# 构建插件
激动人心的时候到了,你几乎做完所有准备工作了,
在右侧点击「Gradle」,依次展开 Tasks
、build
,双击图中选中的 build
开始构建
左侧转到「运行」标签页,出现「成功」、「BUILD SUCCESSFUL in XX」就代表成功了。
构建出来的插件 jar 会出现在 build/libs 文件夹里,复制到你的服务器里测试吧!
插件启用后,如期在日志中输出了「你好,Minecraft!」
# 正式开始了解Bukkit
你成功地在 Minecraft 服务器上运行了你的第一个插件,小打小闹结束了。接下来,我们基于你刚刚配置好的项目,正式开始插件开发。
我们需要知道,Bukkit能干什么。翻阅 Bukkit 的 Javadoc (opens new window) (俗称 BukkitAPI) 可以知晓各个类的注释。不过全都是英文的,读起来想必非常枯燥,特别是对于没有 Java 基础的人来说,更是没有兴趣读下去。
简单总结一下,插件可以抽象成以下几种能力:
- 监听游戏事件,作出自定义行为操作或者修改事件结果 (修改参数或者取消执行),
- 通过命令主动执行操作。
- 通过定时任务执行操作。
- 对数据 (包括配置和插件数据等等) 的增删改查,不管是存到插件目录还是数据库。
你要做的,就是如何活用这几种能力,来实现你想要的具体功能。
接下来该轮到你发挥了,
提示
如果你从未接触过编程,查阅以下几条规则来帮你快速写出代码。
点击查看
我知道这很长,请认真读完。
学习一门新编程语言的宗旨是,先了解基本语法结构,遇到不会的东西就去网上查,去问人。代码抄得多了你就能够逐渐了解为什么要这么写了。
尽量少问 GPT,AI 或许能很快地给出答案,但答案的正确率不一定比人高,优先寻找人类编写的帖子。
最好加入一个技术交流群。只要不是太过于低级的、网上能查到的问题,程序员们通常乐于回复你的问题。
你作为初学者,我并不要求你可以做到理解面向对象、把方法玩出花来、使用泛型等等,这对你来说可能还太早了。
有时候你可能按照教程输入代码会报错,鼠标移到爆红的代码上面,如果有显示「导入类」就点击它。如果显示了多个结果,尽可能选择位于 org.bukkit
、java.util
包的类。
在 Java 中,以 //
开头的是注释 (字体会显示为灰色),注释不参与代码编译,仅仅是给人阅读的一串说明,之后的代码会写一些注释以方便读者理解。
在 Java,代码要写在方法里,而监听游戏事件最好的方法是新建方法,需要监听事件的时候,直接抄下面的代码就好了。
@EventHandler
public void on(事件类型 e) {
// 这里花括号包裹的区域叫作「方法体」
// 在方法体里面,写事件触发之后执行的代码
}
2
3
4
5
在 Java,我们用.
来进入下一级,注意到上面代码中的 e
了吗?那就是触发事件时的数据(这样描述便于理解,但并不准确),
把它想象成一个「房间」,我们可以在它后面加一个.
并加上门牌号来打开特定的「门」,进入它后面的房间。
比如 玩家进入服务器 这个事件,e.getPlayer()
的大致意思就是 事件数据.获取玩家()
。
同理,e.getPlayer().getName()
的大致意思就是 事件数据.获取玩家().获取名字()
。
在你输入
.
的那一刻,IDEA 会弹出一个跟输入法一样的「智能提示」框,它会告诉你,这个「房间」里面有什么「门」可以进。
在一个方法体里面,总体上来说,代码是自上而下,自左向右执行的。有一些特殊情况会从右往左、或者有优先级地执行,大多数情况下是自左向右执行的。每条完整语句的后面都需要有一个英文分号;
结尾。
你肯定有注意到这里有括号,事物.方法()
,这对括号就是用来执行相应的方法的,方法你可以理解为机器上的一个按钮,执行方法就会把这个按钮给按下去,产生相应的效果。
而这个按钮后面接的是什么东西,不需要你去考虑,你只要知道它能产生什么效果就可以了。
有一些方法的括号里面可以填写参数,用来影响产生的效果。比如 玩家在物品栏移动物品 这个事件,e.setCancelled(true);
的大致意思就是 事件数据.设置是否取消事件(是)
,执行这个方法,会使得玩家无法在物品栏移动物品。这也是做菜单的时候,防止玩家拿走菜单图标的最基本方法。
还有一些方法有返回值,就比如 e.getPlayer()
,它返回的是事件的玩家数据,所以你可以再在它后面加 .getName()
获取玩家的名称。
Java 语法是区分大小写的,大小写不同的两段代码,含义完全不同。
这里顺便讲讲变量,变量可以把 我们之前比喻的「门」 的门牌号给记住,让你的代码更简洁一点,
这样就不用每次想要获取玩家数据的时候,都从事件数据出发了。如下所示,新建变量的格式是:
类型 变量名 = 值;
简化 e.getPlayer().getName()
和 e.getPlayer().getLevel()
的示例如下:
Player player = e.getPlayer();
String name = player.getName();
int level = player.getLevel();
2
3
此外,还有 if (条件) { 满足条件执行操作 } else { 不满足条件执行操作 }
、return;
(结束执行) 等等初学阶段比较常用的语法,不多赘述。
Java 的门道很多,本教程只能引你入门,教你所有的语法就太冗长了。更多的语法规则和用法,需要你之后去寻找通用的 Java 教程去补齐。
# 命令系统
未完成
# 事件系统
未完成
# 定时器系统
未完成
# 配置系统
未完成