这是一个完整的 Nukkit-MOT 插件开发模板,展示了各种常用功能的实现方式。
- 目标服务端: Nukkit MOT (Minecraft Bedrock Edition 服务端)
- 开发语言: Java / Kotlin
- 构建工具: Gradle
- 最低支持版本: 1.0.0+
src/main/java/cn/nukkitmot/exampleplugin/
├── camera/ # 镜头控制 API
├── command/ # 命令系统
├── config/ # 配置文件处理
├── custom/ # 自定义内容
│ ├── enchantment/ # 自定义附魔
│ ├── entity/ # 自定义实体
│ └── item/ # 自定义物品
├── form/ # 表单系统
├── loader/ # 库加载器
├── nbs/ # NoteBlockSound 音乐系统
├── scoreboard/ # 计分板系统
├── BroadcastPluginTask.java # 定时任务示例
├── EventListener.java # 事件监听示例
└── ExamplePlugin.java # 插件主类
提供玩家镜头控制的完整功能,支持固定镜头、跟随镜头、预设镜头和运镜序列。
| 类名 | 功能 |
|---|---|
| CameraManager | 镜头管理器,提供所有镜头控制 API |
| CameraMode | 镜头模式枚举 |
| CameraPreset | 预设镜头配置 |
| CameraPosition | 镜头位置数据 |
| CameraSequence | 运镜序列(多关键帧动画) |
| CameraInstruction | 镜头指令封装 |
// 在插件 onEnable() 中初始化
CameraManager.init(this);
// 在插件 onDisable() 中清理
CameraManager.getInstance().clearAllCameras();CameraManager camera = CameraManager.getInstance();
// 1. 固定镜头 - 设置指定位置
// 参数: 玩家, X, Y, Z, 缓动时间(秒)
camera.setFixedCamera(player, 100, 50, 100, 2.0f);
// 带旋转角度的固定镜头
// 参数: 玩家, X, Y, Z, 俯仰角, 偏航角, 缓动时间
camera.setFixedCamera(player, 100, 50, 100, 30, 45, 2.0f);
// 2. 跟随镜头 - 玩家走到哪里镜头跟到哪里
// 参数: 玩家, X偏移, Y偏移, Z偏移, 缓动时间
// 示例: 在玩家后方5格、上方2格处跟随
camera.setFollowCamera(player, 0, 2, -5, 1.5f);
// 3. 关闭镜头 - 恢复正常视角
camera.clearCamera(player);
// 清除所有玩家的镜头
camera.clearAllCameras();// 4. 2D侧面视角 - 适合横版游戏风格
// 左侧视角,距离8格
camera.set2DCamera(player, CameraManager.CameraSide.LEFT, 8.0, 1.5f);
// 右侧视角,距离8格
camera.set2DCamera(player, CameraManager.CameraSide.RIGHT, 8.0, 1.5f);
// 其他预设镜头
// 俯视视角
camera.setTopDownCamera(player, 15.0, 1.0f);
// 等距视角
camera.setIsometricCamera(player, 10.0, 1.0f);
// 使用预设枚举
camera.setPresetCamera(player, CameraPreset.FIRST_PERSON, 0, 1.0f);
camera.setPresetCamera(player, CameraPreset.THIRD_PERSON, 5, 1.0f);
camera.setPresetCamera(player, CameraPreset.FREE, 0, 1.0f);// 5. 运镜序列 - 创建复杂的镜头动画
CameraSequence sequence = camera.createSequence(player)
// 添加第一帧: 位置 + 持续时间(tick) + 缓动类型
.addFrame(new CameraPosition(100, 50, 100), 40, CameraInstruction.EaseType.IN_OUT_SINE)
// 添加第二帧
.addFrame(new CameraPosition(120, 60, 120), 40)
// 添加第三帧
.addFrame(new CameraPosition(140, 50, 100), 40);
// 播放序列
sequence.play();
// 暂停
sequence.pause();
// 停止
sequence.stop();
// 设置循环播放
sequence.setLoop(true);
// 设置完成回调
sequence.onComplete(() -> {
player.sendMessage("运镜完成!");
});// 创建环绕玩家的圆形运镜
CameraSequence orbitSequence = camera.createSequence(player)
.addOrbitFrames(
10.0, // 环绕半径
5.0, // 高度
0, // 起始角度(度)
360, // 结束角度(度)
100, // 总持续时间(tick)
20 // 帧数
)
.setLoop(false);
orbitSequence.play();支持的缓动类型:
LINEAR- 线性匀速IN_OUT_SINE- 正弦缓入缓出(推荐)IN_OUT_QUAD- 二次缓入缓出IN_OUT_CUBIC- 三次缓入缓出IN_OUT_QUART- 四次缓入缓出IN_OUT_QUINT- 五次缓入缓出IN_OUT_EXPO- 指数缓入缓出IN_OUT_CIRC- 圆形缓入缓出IN_OUT_BACK- 回弹缓入缓出IN_OUT_ELASTIC- 弹性缓入缓出IN_OUT_BOUNCE- 弹跳缓入缓出SPRING- 弹簧效果
// 检查玩家是否有活动镜头
boolean hasCamera = camera.hasActiveCamera(player);
// 获取玩家当前镜头状态
CameraManager.CameraState state = camera.getPlayerCameraState(player);
// 获取当前镜头模式
CameraMode mode = state.getMode();
// 获取当前镜头位置
CameraPosition position = state.getPosition();
// 检查是否是相对位置(跟随模式)
boolean isRelative = state.isRelative();展示 Nukkit 插件命令的完整实现方式。
| 类名 | 功能 |
|---|---|
| ExampleCommand | 示例命令类 |
// 在 plugin.yml 中注册命令
commands:
examplecommand:
description: "示例命令"
usage: "/examplecommand <参数>"
aliases: ["test"]
permission: exampleplugin.command.example
// 在主类中初始化
@Override
public void onEnable() {
this.getServer().getCommandMap().register("exampleplugin", new ExampleCommand());
}public class ExampleCommand extends PluginCommand<ExamplePlugin> {
public ExampleCommand() {
super("examplecommand", ExamplePlugin.getInstance());
// 设置多语言描述
this.setDescription("%exampleplugin.examplecommand.description");
this.setAliases(new String[]{"test"});
// 清空默认参数
this.getCommandParameters().clear();
// 模式1: 枚举参数
this.getCommandParameters().put("pattern1", new CommandParameter[]{
CommandParameter.newEnum("enum1", false,
new CommandEnum("method", "say1", "sayone")),
});
// 模式2: 多参数组合
this.getCommandParameters().put("pattern2", new CommandParameter[]{
CommandParameter.newEnum("enum2", false, new String[]{"say2"}),
CommandParameter.newType("player", false, CommandParamType.TARGET),
CommandParameter.newType("message", true, CommandParamType.STRING)
});
}
@Override
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
// 命令执行逻辑
return true;
}
}| 类型 | 说明 |
|---|---|
CommandParamType.INT |
整数 |
CommandParamType.FLOAT |
浮点数 |
CommandParamType.STRING |
字符串 |
CommandParamType.TARGET |
目标玩家 |
CommandParamType.BLOCK_POSITION |
方块位置 |
CommandParamType.POSITION |
位置 |
CommandParamType.MESSAGE |
消息文本 |
展示 YAML 配置文件的读取和写入。
| 类名 | 功能 |
|---|---|
| ExampleConfig | 配置管理类 |
public class ExampleConfig {
private final Config config;
public ExampleConfig() {
// 从 resources 解压默认配置
ExamplePlugin.getInstance().saveResource("config.yml");
// 加载配置
config = new Config(
new File(ExamplePlugin.getInstance().getDataFolder(), "config.yml"),
Config.YAML,
// 默认值
new ConfigSection(new LinkedHashMap<>() {{
put("this-is-a-key", "Hello! Config!");
put("another-key", true);
put("object-key", new LinkedHashMap<String, Object>() {{
put("enabled", false);
put("subKey1", "nukkit");
}});
}})
);
}
// 读取配置
public String getString(String key) {
return config.getString(key);
}
public int getInt(String key) {
return config.getInt(key);
}
public boolean getBoolean(String key) {
return config.getBoolean(key);
}
// 保存配置
public void save() {
config.save();
}
}// 初始化
ExampleConfig config = new ExampleConfig();
// 读取
String value = config.getString("this-is-a-key");
int number = config.getInt("number-key");
// 修改并保存
config.set("this-is-a-key", "新值");
config.save();| 类名 | 功能 |
|---|---|
| CandyCaneSword | 自定义剑类物品 |
public class CandyCaneSword extends ItemCustom {
public CandyCaneSword() {
super("nukkit:candy_cane_sword", null, "candy_cane_sword");
}
@Override
public CustomItemDefinition getDefinition() {
return CustomItemDefinition
.simpleBuilder(this, null)
.allowOffHand(true) // 允许副手
.handEquipped(true) // 作为主手装备
.build();
}
@Override
public int getMaxDurability() {
return 500; // 耐久度
}
@Override
public int getMaxStackSize() {
return 1; // 最大堆叠
}
}// 在插件主类中注册
private void registerItems() {
Item.registerCustomItem(CandyCaneSword.class);
}| 类名 | 功能 |
|---|---|
| MarkerEntity | 自定义标记实体 |
// 注册自定义实体
EntityManager.get().registerDefinition(
CustomEntityDefinition.builder()
.identifier("nukkit:marker")
.summonable(true)
.spawnEgg(true)
.implementation(MarkerEntity.class)
.build()
);| 类名 | 功能 |
|---|---|
| AutoRemeltedEnchatment | 自定义附魔 |
展示 Nukkit 表单的完整实现。
| 类名 | 功能 |
|---|---|
| DemoSimpleForm | 简单表单(按钮列表) |
| DemoCustomForm | 自定义表单(输入框、下拉等) |
| DemoModalForm | 模式表单(是/否) |
| DemoDialogForm | 对话框表单 |
| DemoScrollingTextDialog | 滚动文本对话框 |
public class DemoSimpleForm {
public static void open(Player player) {
FormWindowSimple form = new FormWindowSimple("标题", "描述文本");
// 添加按钮
form.addButton(new ElementButton("按钮1"));
form.addButton(new ElementButton("按钮2"));
// 处理点击
form.addHandler(FormResponseHandler.withoutPlayer(ignored -> {
if (form.wasClosed()) return;
int buttonId = form.getResponse().getClickedButtonId();
String buttonText = form.getResponse().getClickedButton().getText();
// 处理不同按钮
switch (buttonId) {
case 0: // 按钮1
break;
case 1: // 按钮2
break;
}
}));
player.showFormWindow(form);
}
}FormWindowCustom form = new FormWindowCustom("自定义表单");
// 添加输入框
form.addElement(new ElementInput("输入框标签", "占位符文本", "默认值"));
// 添加下拉菜单
form.addElement(new ElementDropdown("下拉菜单", Arrays.asList("选项1", "选项2", "选项3")));
// 添加滑块
form.addElement(new ElementSlider("滑块", 0, 100, 1, 50));
// 添加开关
form.addElement(new ElementToggle("开关", true));
// 处理提交
form.addHandler(FormResponseHandler.withoutPlayer(ignored -> {
if (form.wasClosed()) return;
String input = form.getResponse().getInputResponse(0);
int dropdownIndex = form.getResponse().getDropdownResponse(1).getElementID();
float sliderValue = form.getResponse().getSliderResponse(2);
boolean toggleValue = form.getResponse().getToggleResponse(3);
}));
player.showFormWindow(form);FormWindowModal form = new FormWindowModal(
"确认",
"确定要执行此操作吗?",
"是", // 确认按钮文本
"否" // 取消按钮文本
);
form.addHandler(FormResponseHandler.withoutPlayer(ignored -> {
if (form.wasClosed()) return;
boolean confirmed = form.getResponse().getClickedButtonId() == 0;
if (confirmed) {
// 确认操作
} else {
// 取消操作
}
}));
player.showFormWindow(form);提供 NBS 文件的播放功能,支持为单个或所有玩家播放音乐。
| 类名 | 功能 |
|---|---|
| NBSSoundManager | NBS 音乐管理器 |
| NBSSongPlayer | NBS 播放器 |
| NBSDecoder | NBS 文件解码器 |
| Song | 歌曲数据 |
| Layer | 音轨层 |
| Note | 音符数据 |
// 在插件 onEnable() 中初始化
NBSSoundManager.init(this);
// 在插件 onDisable() 中停止所有音乐
NBSSoundManager.getInstance().stopAll();NBSSoundManager nbs = NBSSoundManager.getInstance();
// 为单个玩家播放
File nbsFile = new File(getDataFolder(), "music/song.nbs");
NBSSongPlayer player = nbs.playNBS(player, nbsFile);
NBSSongPlayer player = nbs.playNBS(player, nbsFile, 100f); // 指定音量
// 在指定位置播放(范围内玩家都能听到)
NBSSongPlayer player = nbs.playNBS(nbsFile, position);
NBSSongPlayer player = nbs.playNBS(nbsFile, position, 100f);
// 为所有玩家播放
NBSSongPlayer player = nbs.playNBSForAll(nbsFile);
NBSSongPlayer player = nbs.playNBSForAll(nbsFile, 100f);// 停止指定播放器
nbs.stopPlayer(playerId);
// 停止玩家的所有音乐
nbs.stopPlayerSongs(player);
// 停止所有音乐
nbs.stopAll();
// 获取播放器
NBSSongPlayer player = nbs.getPlayer(playerId);NBSSongPlayer player = nbs.playNBS(...);
// 添加/移除听众
player.addPlayer(anotherPlayer);
player.removePlayer(player);
// 控制播放
player.setPlaying(false); // 暂停
player.setPlaying(true); // 继续
player.setFadeTime(10); // 设置淡入淡出时间(tick)
player.setAutoDestroy(true); // 播放完自动销毁展示计分板的完整实现。
| 类名 | 功能 |
|---|---|
| ScoreBoardAPI | 计分板 API |
| BoardManager | 计分板管理器 |
| IBoard | 计分板接口 |
| NukkitBoardImpl | Nukkit 计分板实现 |
// 初始化
BoardManager boardManager = new BoardManager(this, new ImplInReal());
ScoreBoardAPI.setManager(boardManager);
// 创建计分板
IBoard board = ScoreBoardAPI.createBoard(player);
// 设置标题
board.setDisplayName("§l§e我的服务器");
// 设置行内容
board.setLines(Arrays.asList(
"§7----------------",
"§f玩家: §a" + player.getName(),
"§f等级: §e10",
"§f金币: §61000",
"§7----------------"
));
// 更新特定行
board.updateLine(2, "§f等级: §e" + newLevel);
// 删除计分板
ScoreBoardAPI.removeBoard(player);展示 Nukkit 定时任务的实现。
| 类名 | 功能 |
|---|---|
| BroadcastPluginTask | 定时广播任务 |
public class BroadcastPluginTask extends PluginTask<ExamplePlugin> {
public BroadcastPluginTask(ExamplePlugin owner) {
super(owner);
}
@Override
public void onRun(int currentTick) {
// 定时执行的逻辑
getOwner().getLogger().info("任务执行于 tick: " + currentTick);
}
}// 延迟任务 - 延迟100tick执行
this.getServer().getScheduler().scheduleDelayedTask(
new BroadcastPluginTask(this),
100
);
// 重复任务 - 每20tick执行一次
this.getServer().getScheduler().scheduleRepeatingTask(
new BroadcastPluginTask(this),
20
);
// 延迟重复任务 - 延迟500tick后,每200tick执行
this.getServer().getScheduler().scheduleDelayedRepeatingTask(
new BroadcastPluginTask(this),
500, // 延迟
200 // 间隔
);
// 异步任务
this.getServer().getScheduler().scheduleAsyncTask(this, new AsyncTask() {
@Override
public void onRun() {
// 异步执行的代码
}
});展示 Nukkit 事件监听器的实现。
| 类名 | 功能 |
|---|---|
| EventListener | 事件监听器 |
public class EventListener implements Listener {
private final ExamplePlugin plugin;
public EventListener(ExamplePlugin plugin) {
this.plugin = plugin;
}
// 玩家加入事件
@EventHandler(priority = EventPriority.NORMAL)
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
event.setJoinMessage("欢迎 " + player.getName() + " 加入服务器!");
}
// 玩家离开事件
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
plugin.getLogger().info(player.getName() + " 离开了服务器");
}
// 玩家聊天事件
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onPlayerChat(PlayerChatEvent event) {
Player player = event.getPlayer();
String message = event.getMessage();
// 拦截特定消息
if (message.contains("脏话")) {
event.setCancelled(true);
player.sendMessage("§c请勿使用不当语言!");
}
}
}// 在插件主类中注册
@Override
public void onEnable() {
this.getServer().getPluginManager().registerEvents(
new EventListener(this),
this
);
}| 事件类 | 说明 |
|---|---|
PlayerJoinEvent |
玩家加入 |
PlayerQuitEvent |
玩家离开 |
PlayerChatEvent |
玩家聊天 |
PlayerInteractEvent |
玩家交互 |
BlockBreakEvent |
方块破坏 |
BlockPlaceEvent |
方块放置 |
EntityDamageEvent |
实体受伤 |
PlayerDeathEvent |
玩家死亡 |
InventoryClickEvent |
背包点击 |
PlayerMoveEvent |
玩家移动 |
展示外部库的加载和管理。
| 类名 | 功能 |
|---|---|
| NukkitLibraryManager | 库管理器 |
| Library | 库定义 |
| Repositories | 仓库配置 |
// 在插件主类中加载库
@Override
public void onLoad() {
NukkitLibraryManager manager = new NukkitLibraryManager(this);
// 添加 Maven 仓库
manager.addRepository(Repositories.MAVEN_CENTRAL);
manager.addRepository(Repositories.JITPACK);
// 添加依赖库
manager.loadLibrary(Library.builder()
.groupId("com.google.code.gson")
.artifactId("gson")
.version("2.10.1")
.build());
// 加载所有库
manager.loadAll();
}展示插件主类的完整结构。
public class ExamplePlugin extends PluginBase {
private static ExamplePlugin instance;
private static PluginI18n i18n;
@Override
public void onLoad() {
instance = this;
// 加载库等预处理
}
@Override
public void onEnable() {
// 保存默认资源
saveResource("config.yml");
// 初始化 i18n
i18n = PluginI18nManager.register(this);
// 初始化各系统
CameraManager.init(this);
NBSSoundManager.init(this);
// 注册命令
getServer().getCommandMap().register("exampleplugin", new ExampleCommand());
// 注册事件监听器
getServer().getPluginManager().registerEvents(new EventListener(this), this);
// 注册定时任务
getServer().getScheduler().scheduleRepeatingTask(
new BroadcastPluginTask(this), 20);
// 注册自定义物品
registerItems();
// 注册自定义实体
registerEntities();
getLogger().info("插件已启用!");
}
@Override
public void onDisable() {
// 清理资源
NBSSoundManager.getInstance().stopAll();
CameraManager.getInstance().clearAllCameras();
getLogger().info("插件已禁用!");
}
public static ExamplePlugin getInstance() {
return instance;
}
public static PluginI18n getI18n() {
return i18n;
}
}# 使用 Gradle 编译
./gradlew build
# 或者在 Windows 上
gradlew.bat build编译后的插件位于 build/libs/ 目录下:
build/libs/
├── ExamplePlugin-1.0.0.jar # 主插件
└── ExamplePlugin-1.0.0-all.jar # 包含依赖的完整包
将 JAR 文件复制到服务器的 plugins/ 目录即可。
在 src/main/resources/lang/ 目录下创建语言文件:
zh_CN.lang:
exampleplugin.helloworld=你好,世界!
exampleplugin.examplecommand.description=示例命令en_US.lang:
exampleplugin.helloworld=Hello, World!
exampleplugin.examplecommand.description=Example command// 发送给玩家
player.sendMessage(i18n.tr(player.getLangCode(), "exampleplugin.helloworld"));
// 使用占位符
player.sendMessage(i18n.tr(player.getLangCode(),
"exampleplugin.welcome", player.getName()));
// 获取服务器默认语言
LangCode serverLang = getServer().getLanguage().getLang();本项目采用 MIT 许可证。
- NukkitMOT Team
- QingTong Development Team