今天1话就教大家如何来入门开发 Minecraft 插件。本视频的学习内容由:https://www.bilibili.com/video/BV1Wa4y1T7Tx 算是我的学习笔记!感谢大家能够阅读这个文章~
一、从创建开始
首先的话本次的运行软件是JetBrain家的 IDEA。我们现在需要坐的是在IDEA页面下载 Minecraft 开发的专用插件:
安装完后我们就开始我们今天的项目吧!
我们这次开发的是Minecraft插件,所以在 Groups 栏中我们需要选择 Plugin 然后版本,然后创建成功的话你能看见以下的内容:
那我们就开始解析下上面的内容:
@Override
public void onEnable() {
// Plugin startup logic
System.out.println("插件启动");
}
// 这一部分就是插件的触发条件 实例中的代码的触发条件是 onEnable 也就是服务器插件启动的时候触发的东西,例子中就是输出插件启动的信息。
@Override
public void onDisable() {
// Plugin shutdown logic
}
// 反过来这个就是服务器关闭的提示。
当当我们编辑完插件可以点击小箭头来进行编译:
要是没有小箭头的话也是可以通过 gradlew 来进行编译:
依次点击 build clean jar 来进行编译,最终编译完的文件就在文件路径下的 \MyFirstPlugin\build\libs
然后就可以直接把 这个 jar 文件直接丢到服务端的 plugins 目录下 启动即可使用(你都看这个教程了 应该会开服吧?)
补充:这个大概就是插件的整个文件结构!基本上 IDEA 都给你完成了,还是比较方便的
二、注册命令
上面的从创建项目到构建代码想必大家都会了吧,现在的话就向前一步,教给大家如何去编写自己插件的第一条命令。首先的话注册指令需要到 Plugin.yml 进行注册:
那我们开始编辑里面的内容:
那我们对里面的内容进行解释(主要讲解注册命令):
Commands:
#命令名称
myplugin:
#命令描述
description: "这是一个注册命令的例子"
#命令权限
permission: MyPlugin.Main
#绑定指令 执行的时候就是 /mp 当然 /myplugin 也可以执行
aliases: mp
然后的话我们就要开始编写代码来激活这个命令了。首先的的话我们在软件包内设置一个 MyCommands 的类:
创建完大概是长这样子的:
那我既然这边创建完文件了,但是他和主类目录是分离开的,那主文件怎么知道创建了个 MyCommands.java 呢?那我们就要到主页面来激活它!
Bukkit.getPluginCommand("myplugin").setExecutor(new MyCommands());
注意的是 myplugin 是要和 plugin.yml 的 Commands 的内容要相似,然后后面的 MyCommands 就是我们刚刚创建的类。那搞完后我们开始对指令的部分进行解析:
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) {
// 这边就是填写输入命令后会执行的操作。
return false;
}
CommandSender | 使用指令的人 |
Command | 指令 |
s | 标签 暂时用不上 |
strings | 相当于一个字符串 比如说你输入了个 /mp xxxx 那他的字符串相当于 xxxx 假如说你现在输入了/mp 1 2 那么Strings会记录成 [1,2] |
那解释完我们就开始编写输入命令后会进行的操作了。
package com.mclzyun.blog.myFirstPlugin;
import org.bukkit.GameMode;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class MyCommands implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) {
// 这边就是填写输入命令后会执行的操作。
String a1 = strings[0];
// 获取字符串的第一个部分
String a2 = strings[1];
// 获取字符串的第二个部分
if (a1.equals("gm") && a2.equals("1")) {
// 判断第一个字段和第二个字段是否相同
// 相当于看看有没有正确输入 /mp gm 1
Player p = (Player) commandSender;
// 强制转换玩家名字赋值在 p 变量上
p.setGameMode(GameMode.CREATIVE);
// 更改玩家游戏模式:创造
}
return false;
}
}
这个示例的代码就是输入命令/mp gm 1 让输入指令的玩家切换到创造模式。
三、游戏事件
什么叫做游戏事件?:
那我们就可以开始学习事件了,还是一样的我们创建一个专属事件的类
那我们搞一个玩家加入服务器事件:
package com.mclzyun.blog.myFirstPlugin;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
// 上面这些不用顾虑 基本 TAB完自己就出来了
public class MyListener implements org.bukkit.event.Listener {
// 设置一个注释 让服务器能够读到这是一个事件
@EventHandler
// 创建一个玩家加入服务器事件
public void onPlayerJoin(PlayerJoinEvent event) { // 这个event 是可以自己随便取名的
// 通过事件获取玩家游戏名 并赋值 name 上
String name = event.getPlayer().getName();
Player player = event.getPlayer();
// 玩家加入服务器发送信息
event.setJoinMessage(name + "加入了服务器");
}
}
基本的注释解释都写出来了,另外视频博主顺便推荐了个 API 站:http://bbs.mcxin.cn/api/bukkit1.12.2/ 这是一个 bukkit API 的网站,就比如说我们的玩家加入事件的内容就是这里面找的:
四、添加操作页面
1、了解容器&创建容器
什么叫做容器?
想必大家都有接触过箱子页面操作,所以我们这边说的操作页面,顾名思义就是容器页面操作!
那我们这边写完后给大家看看示例代码:
package com.mclzyun.blog.myFirstPlugin;
import org.bukkit.GameMode;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class MyCommands implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) {
// 这边就是填写输入命令后会执行的操作。
// /mp help
if (args.length == 1 && args[0].equals("help")) {
// 判断输入的命令是否为 help
commandSender.sendMessage("插件帮助!\n" +
"/mp help - 显示帮助信息\n" +
"/mp gm 1 - 切换到创造模式\n" +
"/myplugin open - 打开插件菜单");
// 如果是就发送一条消息给玩家
return true;
}
// /mp gm 1
if (args.length = 2 && args[0].equals("gm") && args[1].equals("1")) {
// 判断第一个字段和第二个字段是否相同
// 相当于看看有没有正确输入 /mp gm 1
Player p = (Player) commandSender;
// 强制转换玩家名字赋值在 p 变量上
p.setGameMode(GameMode.CREATIVE);
// 更改玩家游戏模式:创造
return true;
}
// /myplugin open
if (args.length == 1 && args[0].equals("open")) {
Inventory inv = Bukkit.createInventory(null, 9, "我的插件菜单"); // null 表示没有特定的拥有者
// 创建一个新的物品栏,大小为9格,标题为“我的插件菜单”
// 这边的9格是物品栏的大小,标题可以自己随便写
Inventory inv2 = Bukkit.createInventory(null,InventoryType.WORKBENCH, "我的插件菜单2");
Player p = (Player) Sender;
p.openInventory(inv);
// 强制转换玩家名字赋值在 p 变量上
// 打开物品栏给玩家
return true;
}
}
}
我们重点是要看这个部分:
if (args.length == 1 && args[0].equals("open")) {
Inventory inv = Bukkit.createInventory(null, 9, "我的插件菜单"); // null 表示没有特定的拥有者
// 创建一个新的物品栏,大小为9格,标题为“我的插件菜单”
// 这边的9格是物品栏的大小,标题可以自己随便写
Inventory inv2 = Bukkit.createInventory(null,InventoryType.WORKBENCH, "我的工作台");
Player p = (Player) Sender;
p.openInventory(inv);
// 强制转换玩家名字赋值在 p 变量上
// 打开物品栏给玩家
return true;
}
另外提一嘴 你会发现我们新增加了一项内容 args,这是什么东西,和前面的 String 相比有什么区别?
args 是命令参数数组(String[] args),而 String s 是命令的别名(通常是主命令名)。 args 不能直接替代 String s 的作用。s 用于获取玩家输入的主命令(比如 /mp),而 args 用于获取命令后面的参数(比如 help、gm 1 等)。
怎么说?拿个例子给你看看!
- 玩家输入 /mp help,s 是 “mp”,args[0] 是 “help”。
- 玩家输入 /mp gm 1,s 是 “mp”,args[0] 是 “gm”,args[1] 是 “1”
接着针对:Inventory inv = Bukkit.createInventory 的 参数展开讲解:
第一个 | 第二个 | 第三个 | |
参数作用 | 拥有者(一般null就行了) | 容器大小(多少格)或者填写容器名称 比如说下面就有一个 WORKBENCH | 容器GUI名称 |
2、容器添加物品
①添加物品
那我们单独剖解一块代码来为大家来解析:
// /myplugin open
if (args.length == 1 && args[0].equals("open")) {
Inventory inv = Bukkit.createInventory(null, 9, "我的插件菜单"); // null 表示没有特定的拥有者
// 创建一个新的物品栏,大小为9格,标题为“我的插件菜单”
// 这边的9格是物品栏的大小,标题可以自己随便写
Inventory inv2 = Bukkit.createInventory(null,InventoryType.WORKBENCH, "我的插件菜单2");
// 创建物品有三种方式
// ItemStack item = new ItemStack(Material.DIAMOND); // 直接获取
ItemStack item2 = new ItemStack(Material.getMaterial("DIAMOND"),1); // 指定名称
// ItemStack item2 = new ItemStack(Material.getMaterial(264)); // 264 是钻石剑的材料ID(不推荐 数字ID高版本不靠谱)
ItemMeta meta = item2.getItemMeta(); // 获取物品的元数据
meta.setDisplayName("§b§l钻石"); // 设置物品名称为蓝色粗体
item2.setItemMeta(meta); // 将修改后的元数据应用
inv.addItem(item2); // 将物品添加到物品栏中
Player p = (Player) Sender;
p.openInventory(inv);
// 强制转换玩家名字赋值在 p 变量上
// 打开物品栏给玩家
return true;
}
相比大家都看得懂这些代码,而至于为什么不推荐用 数字ID 这个作为创建物品的依据。是因为数字ID会随着版本的更新而变动,包括你会发现现在高版本通过 GIVE 来获取物品的时候都是要指定物品名称才可以拿到了。
然后的话现在要唯一解释的代码是:ItemStack item2 = new ItemStack(Material.getMaterial
第一个参数 | 第二个参数 | 第三个参数 | |
参数解释 | 物品名称(建议英文大写) | 物品数量(展示数量) | 小号ID (高版本没用 忽视!) |
但是 当你插件构建完后 会发现一个严重的问题!你放上去的东西可以拿下来,这不就变成了刷物品了吗?
②添加点击物品事件
所以我们要针对这个事情,来专门设置一个点击钻石的事件,来看看我们的示例代码:
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
Inventory inv = event.getInventory(); // 获取玩家点击的物品栏
String title = inv.getTitle(); // 获取物品栏的标题
if (title.equals("我的插件菜单")) { // 判断物品栏标题是否为 "我的插件菜单"
// 如果是就发送一条消息给玩家
player.sendMessage("你点击了物品栏:" + title);
ItemStack item2 = new ItemStack(Material.getMaterial("IRON_INGOT"), 1); // 创建一个铁锭
ItemMeta meta = item2.getItemMeta(); // 获取物品的元数据
meta.setDisplayName("§b§l铁锭"); // 设置物品名称
List<String> lore = new ArrayList<>(); // 创建一个物品描述列表
lore.add("§7这是一个铁锭"); // 添加描述文本
lore.add("§7可以用来制作工具和盔甲"); // 添加更多描述文本
meta.setLore(lore); // 设置物品描述
item2.setItemMeta(meta); // 将修改后的元数据应用
Player p = (Player) event.getWhoClicked(); // 获取点击物品栏的玩家
p.getInventory().addItem(item2); // 将铁锭添加到玩家的物品栏中
event.setCancelled(true); // 取消事件,防止物品被移动
}
注意:这个代码是在监听事件 MyListener.java 里面的
这个代码其实注释给到位了,但是我还是要提一嘴,那个判断物品是否标题相同一定要一样的!
诶 你会发现我代码种 出现了个奇怪的东西:§ 这玩意是什么?怎么打出来的?这个东西叫做颜色代码,可以让你的物品名称(name) 和 描述文本(lore) 有颜色。那§后面的7 b l 就是他的代号,这边给大家补充一个代号表
把 & 改成§就行了,老版本一般比较多的是 &,但是我还是建议使用 § 来作为颜色的代号!那怎么打出来这个效果勒?有两种方式:
- 在键盘上按着 ALT键(不要松)+ 167(依次在小键盘键入)
- 输入法输入 zjh 就可以跳出来了(如图)
五、配置文件的生成
然后的话这边展示一些常见的 YAML 语法的格式:
key: "ZherKing" # String
key1: 123 # int
key2:
- "ZherKing" # List<String>
- "ZherKing2"
key3:
- 1
- 2 # List<int>
key4: #多层级
key5: "ZherKing" # Map<String, String>
key6: 123
key7: false # boolean
如果说想学更多的YAML语法的话可以移步到这边:https://www.runoob.com/w3cnote/yaml-intro.html
那我们就直接代入到插件中实践:
比如说我们这里有一个监听事件,监听玩家加入服务器的提示文本。当我们想修改这些内容的时候,每次修改都得重新编译,太麻烦了!直接加入配置文件中!
joinMessage: "欢迎加入服务器, %player%!"
这是我们在 config 中添加的内容 其中 %player% 就是一个玩家名变量
转到服务器启动这边,服务器启动的时候我们也要设置一下启动配置文件的相关内容:
saveDefaultConfig(); // 保存默认配置文件
然后的话我们要设置一个 reload 方便服务器在运行的时候,输入指令重启一个插件来重新加载配置文件,当然你会发现在 Command 类发现没办法直接调用 ReloadConfig 类,这时候需要我们在主类添加内容:
static MyFirstPlugin main;
main = this;
这两个什么意思呢?:
static MyFirstPlugin main; 声明了一个静态变量,用于保存插件的主类实例。
main = this; 在 onEnable() 方法中赋值,把当前插件实例赋给 main,这样你可以在其他类通过 MyFirstPlugin.main 访问插件主类对象,实现跨类调用插件的方法或属性。
这时候我们可以在我们的命令类发现可以添加 ReloadConfig 了:
if (args.length == 1 && args[0].equals("reload")) {
MyFirstPlugin.main.reloadConfig();
commandSender.sendMessage("插件配置文件已重载!");
return true;
}
这样把 Reload 的指令注册之后,我们就回到监听事件,去吧我们刚刚添加的 JoinMessage 给放入在玩家加入服务器的提示语中
// 设置一个注释 让服务器能够读到这是一个事件
@EventHandler
// 创建一个玩家加入服务器事件
public void onPlayerJoin(PlayerJoinEvent event) { // 这个event 是可以自己随便取名的
// 强制将玩家游戏名转换成 name 变量
String name = event.getPlayer().getName();
Player player = event.getPlayer();
String msg = MyFirstPlugin.main.getConfig().getString("joinMessage");
// 玩家加入服务器发送信息
event.setJoinMessage(name + "加入了服务器");
}
重点在这边:
String msg = MyFirstPlugin.main.getConfig().getString("joinMessage");
这边的话我们就是将 (插件主类 获取配置文件 得到字符串) 的内容直接赋值到 msg 上,注意 joinMessage 这个字符串要和 config.yml 的内容一样
那如果说你想搞一个分层级的内容怎么处理?
msg:
msg1: "这是一个测试消息1" # 就像这个一样
这样子就行了,还是很好理解的:
String msg = MyFirstPlugin.main.getConfig().getString("msg.msg1");
既然理解之后,我们就直接把玩家加入服务器的提示语直接进行更换:
event.setJoinMessage(msg.replace("%player%", name));
其中的话我们创建的 %player% 变量是需要进行更换的,更换成我们原本就有的玩家名字即可:
五、代码总览
那我们这次小小的教程就给大家教完了,后面的话我们可能会有一些比较进阶性的教程或者插件实践来分享给大家。下面是本次教程的全部源码:
MyCommands.java:
package com.mclzyun.blog.myFirstPlugin;
import org.bukkit.GameMode;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.bukkit.Bukkit;
import org.bukkit.inventory.Inventory;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.Material;
import org.bukkit.inventory.meta.ItemMeta;
public class MyCommands implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) {
// 这边就是填写输入命令后会执行的操作。
// /mp help
String[] args = strings; // 将输入的命令参数赋值在 args 变量上
if (args.length == 1 && args[0].equals("help")) {
// 判断输入的命令是否为 help
commandSender.sendMessage("插件帮助!\n" +
"/mp help - 显示帮助信息\n" +
"/mp gm 1 - 切换到创造模式\n" +
"/myplugin open - 打开插件菜单" +
"/mp reload - 重启插件\n" );
// 如果是就发送一条消息给玩家
return true;
}
if (args.length == 1 && args[0].equals("reload")) {
MyFirstPlugin.main.reloadConfig();
commandSender.sendMessage("插件配置文件已重载!");
return true;
}
// /mp gm 1
if (args.length == 2 && args[0].equals("gm") && args[1].equals("1")) {
// 判断第一个字段和第二个字段是否相同
// 相当于看看有没有正确输入 /mp gm 1
Player p = (Player) commandSender;
// 强制转换玩家名字赋值在 p 变量上
p.setGameMode(GameMode.CREATIVE);
// 更改玩家游戏模式:创造
return true;
}
// /myplugin open
if (args.length == 1 && args[0].equals("open")) {
Inventory inv = Bukkit.createInventory(null, 9, "我的插件菜单"); // null 表示没有特定的拥有者
// 创建一个新的物品栏,大小为9格,标题为“我的插件菜单”
// 这边的9格是物品栏的大小,标题可以自己随便写
Inventory inv2 = Bukkit.createInventory(null,InventoryType.WORKBENCH, "我的插件菜单2");
// 创建物品有三种方式
// ItemStack item = new ItemStack(Material.DIAMOND); // 直接获取
ItemStack item2 = new ItemStack(Material.getMaterial("DIAMOND"),1); // 指定名称
// ItemStack item2 = new ItemStack(Material.getMaterial(264)); // 264 是钻石剑的材料ID(不推荐 数字ID高版本不靠谱)
ItemMeta meta = item2.getItemMeta(); // 获取物品的元数据
meta.setDisplayName("§b§l钻石"); // 设置物品名称为蓝色粗体
item2.setItemMeta(meta); // 将修改后的元数据应用
inv.addItem(item2); // 将物品添加到物品栏中
Player p = (Player) commandSender;
p.openInventory(inv);
// 强制转换玩家名字赋值在 p 变量上
// 打开物品栏给玩家
return true;
}
return false;
}
}
MyFirstPlugin.java:
package com.mclzyun.blog.myFirstPlugin;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public final class MyFirstPlugin extends JavaPlugin {
static MyFirstPlugin main;
@Override
public void onEnable() {
// Plugin startup logic
System.out.println("插件正在启动!");
Bukkit.getPluginCommand("myplugin").setExecutor(new MyCommands());
Bukkit.getPluginManager().registerEvents(new MyListener(), this);
saveDefaultConfig(); // 保存默认配置文件
main = this;
}
@Override
public void onDisable() {
// Plugin shutdown logic
System.out.println("插件已经关闭!感谢使用!");
}
}
MyListener.java:
package com.mclzyun.blog.myFirstPlugin;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.inventory.Inventory;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
// 上面这些不用顾虑 基本 TAB完自己就出来了
public class MyListener implements org.bukkit.event.Listener {
// 设置一个注释 让服务器能够读到这是一个事件
@EventHandler
// 创建一个玩家加入服务器事件
public void onPlayerJoin(PlayerJoinEvent event) { // 这个event 是可以自己随便取名的
// 强制将玩家游戏名转换成 name 变量
String name = event.getPlayer().getName();
Player player = event.getPlayer();
String msg = MyFirstPlugin.main.getConfig().getString("joinMessage");
String msg1 = MyFirstPlugin.main.getConfig().getString("msg.msg1");
// 玩家加入服务器发送信息
event.setJoinMessage(msg.replace("%player%", name));
}
// 创建一个玩家点击物品栏事件
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
Inventory inv = event.getInventory(); // 获取玩家点击的物品栏
Player player = (Player) event.getWhoClicked(); // 获取点击物品栏的
String title = event.getView().getTitle(); // 获取物品栏的标题
if (title.equals("我的插件菜单")) { // 判断物品栏标题是否为 "我的插件菜单"
// 如果是就发送一条消息给玩家
player.sendMessage("你点击了物品栏:" + title);
ItemStack item2 = new ItemStack(Material.getMaterial("IRON_INGOT"), 1); // 创建一个钻石物品
ItemMeta meta = item2.getItemMeta(); // 获取物品的元数据
meta.setDisplayName("§b§l铁锭"); // 设置物品名称
List<String> lore = new ArrayList<>(); // 创建一个物品描述列表
lore.add("§7这是一个铁锭"); // 添加描述文本
lore.add("§7可以用来制作工具和盔甲"); // 添加更多描述文本
meta.setLore(lore); // 设置物品描述
item2.setItemMeta(meta); // 将修改后的元数据应用
Player p = (Player) event.getWhoClicked(); // 获取点击物品栏的玩家
p.getInventory().addItem(item2); // 将铁锭添加到玩家的物品栏中
event.setCancelled(true); // 取消事件,防止物品被移动
}
}
}
config.yml:
joinMessage: "欢迎加入服务器, %player%!"
msg:
msg1: "这是一个测试消息1"
plugin.yml:
name: MyFirstPlugin
version: '1.0-SNAPSHOT'
main: com.mclzyun.blog.myFirstPlugin.MyFirstPlugin
api-version: '1.21'
authors: [ ZherKing ]
description: a plugin can run the command on time
website: blog.mclzyun.com
commands:
myplugin:
description: "这是一个注册命令的例子"
permission: MyPlugin.Main
usage: /<command>
aliases: mp
感谢大家阅读我的博客!