Skip to content

认识 NMS 和 OBC ​

  • NMS:net.minecraft.server,存放 Minecraft 服务端的游戏逻辑代码
  • OBC:org.bukkit.craftbukkit,Bukkit API 的实现层,本质是 NMS 的封装

Bukkit API 涵盖很多功能,但不是万能的。当你想做的事 API 做不到,才需要碰 NMS。本教程以 Spigot 为例(CraftBukkit 也由 SpigotMC 维护,Bukkit 分支基本都基于 Spigot)。

用 NMS 之前:你真的需要吗? ​

Spigot 核心开发者 md_5 的话:Wait! 你真的需要 NMS 吗?

NMS 不是 API。遇到需求不该第一时间考虑 NMS 或发包。 令人费解的是,很多人专门用 NMS 做 ScoreBoard、BossBar、粒子这些API 早就有的事。

每次想用 NMS,先问自己:

  1. 我真的需要 NMS 做这个吗?
  2. 有没有现成 API 能实现?
  3. 我能不能贡献/提议一个 API?

滥用 NMS 的后果

  • 插件失去版本迁移能力:NMS 混淆名每版不同,绑死单版本
  • 阻碍 API 发展、树立坏榜样

能用 API 就别碰 NMS。这是社区共识。

开发环境准备 ​

由于 DMCA 事件,Bukkit 不直接提供 NMS。你需把构建好的服务端 jar引入项目:

以 Gradle 为例:

  1. 项目根目录建 libs/ 文件夹
  2. 把构建好的服务端 jar(如 spigot-1.12.2.jar)拖进去
  3. build.gradle 里配置本地依赖:
    gradle
    dependencies {
        implementation files('libs/spigot-1.12.2.jar')
    }
  4. 同步 Gradle,就能 import NMS 的类了

Maven 类似

Maven 用 systemPath 或 mvn install 到本地仓库。IDEA 里也可直接 Project Structure → Libraries 加 jar。

从 API 背后拿 NMS 对象 ​

直接打印一个 Player 对象,你会得到 CraftPlayer{name=玩家名}——不是 NMS 里的 EntityPlayer。因为 Bukkit API 背后有 OBC 这一层。

OBC 是 NMS 的封装,操作 NMS 封装成 API 就是 OBC 干的事。要拿到 API 背后的 NMS 对象,得强转 + 调对应方法:

java
// Player 背后其实是 CraftPlayer(OBC)
CraftPlayer craftPlayer = (CraftPlayer) player;
// 拿到 NMS 的 EntityPlayer
EntityPlayer entityPlayer = craftPlayer.getHandle();

怎么知道有哪些方法?

用反编译工具(或 IDEA 直接查看类)看 CraftPlayer 源码,找它暴露了哪些 NMS 对象的访问方法。getHandle() 是最常见的入口。

反射与版本兼容 ​

NMS 的类名、方法名是混淆的,每个版本都不一样。直接 import 写死,换个版本就崩。要做版本兼容,得用反射按版本动态调:

java
import org.bukkit.Bukkit;

public class NMSUtil {
    private static String version;

    static {
        // 如 org.bukkit.craftbukkit.v1_12_R1.CraftPlayer
        version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
    }

    public static Class<?> getCraftClass(String name) throws ClassNotFoundException {
        return Class.forName("org.bukkit.craftbukkit." + version + "." + name);
    }

    public static String getVersion() {
        return version; // v1_12_R1
    }
}

按 version 分支调不同版本的 NMS:

java
Object craftPlayer = NMSUtil.getCraftClass("entity.CraftPlayer").cast(player);
Method getHandle = craftPlayer.getClass().getMethod("getHandle");
Object entityPlayer = getHandle.invoke(craftPlayer);

反射又慢又脆

反射有性能开销,且编译期查不出错(方法名拼错运行时才崩)。能用 API 就别用 NMS,必须用 NMS 就尽量封装好、覆盖目标版本。

现代方案:ProtocolLib / Paper ​

  • ProtocolLib:封装了 NMS 发包/反射,自动处理版本兼容。绝大多数发包需求用它,别自己写反射
  • Paper 1.20.5+:改用 Mojang 官方映射(mojang-mapped),NMS 类名不再是混淆名,可读性大增,但跨核心仍需注意

写在最后

NMS 是深水区。新手遇到"API 做不到"的需求,先找现成插件/ProtocolLib 是否已解决,再考虑碰 NMS。本教程只带你入门"怎么用",不教授 NMS 里有什么——那需要你反编译服务端自己探索。


下一篇自定义发包——把 NMS 用在"发数据包给客户端"这个最常见场景。