端(Sides)
文章翻译自官方文档:
端(Sides)
与许多其他程序一样,Minecraft 遵循客户端-服务器架构,其中客户端负责显示数据,而服务器负责更新数据。当我们使用这些术语时,我们对它们的含义有一个相当直观的理解……对吧?
事实证明,并非如此。许多混淆源于 Minecraft 根据上下文有两种不同的端概念:物理端和逻辑端。
逻辑端与物理端
物理端
当您打开 Minecraft 启动器,选择一个 Minecraft 安装并按下“播放”按钮时,您启动了一个物理客户端。这里的“物理”一词用于表示“这是一个客户端程序”。这意味着客户端功能(例如所有渲染内容)在此可用,并且可以根据需要使用。相比之下,物理服务器(也称为专用服务器)是当您启动 Minecraft 服务器 JAR 时启动的内容。虽然 Minecraft 服务器带有一个基本的 GUI,但它缺少所有仅客户端的功能。最显著的是,这意味着服务器 JAR 中缺少各种客户端类。在物理服务器上调用这些类将导致缺少类错误,即崩溃,因此我们需要防范这种情况。
逻辑端
逻辑端主要关注 Minecraft 的内部程序结构。逻辑服务器是游戏逻辑运行的地方。诸如时间和天气变化、实体更新、实体生成等都在服务器上运行。各种数据(例如库存内容)也是服务器的责任。另一方面,逻辑客户端负责显示所有需要显示的内容。Minecraft 将所有客户端代码隔离在 net.minecraft.client
包中,并在一个称为“渲染线程”的单独线程中运行它,而其他所有内容都被视为公共代码(即客户端和服务器共用的代码)。
有什么区别?
物理端和逻辑端之间的区别最好通过两种场景来说明:
- 玩家加入多人游戏世界:这相当简单。玩家的物理(和逻辑)客户端连接到位于其他地方的物理(和逻辑)服务器——玩家不关心服务器在哪里;只要他们可以连接,这就是客户端所知道的全部,也是客户端需要知道的全部。
- 玩家加入单人游戏世界:这里事情变得有趣了。玩家的物理客户端启动一个逻辑服务器,然后以逻辑客户端的角色连接到同一台机器上的逻辑服务器。如果您熟悉网络,可以将其视为连接到
localhost
(仅在概念上;实际上没有涉及套接字或类似的东西)。
这两种场景也展示了主要问题:如果逻辑服务器可以与您的代码一起工作,这并不能保证物理服务器也能与您的代码一起工作。这就是为什么您应该始终使用专用服务器进行测试以检查意外行为。由于客户端和服务器分离不正确而导致的 NoClassDefFoundError
和 ClassNotFoundException
是 Mod 开发中最常见的错误之一。另一个常见的错误是使用静态字段并从两个逻辑端访问它们;这尤其棘手,因为通常没有任何迹象表明有问题。
提示:如果您需要将数据从一端传输到另一端,您必须发送一个数据包。
在 NeoForge 代码库中,物理端由一个名为 Dist
的枚举表示,而逻辑端由一个名为 LogicalSide
的枚举表示。
信息:历史上,服务器 JAR 中有客户端没有的类。在现代版本中不再是这样;物理服务器可以说是物理客户端的一个子集。
执行端特定操作
Level#isClientSide()
这个布尔检查将是您检查端的最常用方式。在 Level
对象上查询此字段可以确定该级别所属的逻辑端:如果此字段为 true
,则该级别在逻辑客户端上运行。如果该字段为 false
,则该级别在逻辑服务器上运行。因此,物理服务器在此字段中将始终为 false
,但我们不能假设 false
意味着物理服务器,因为此字段在物理客户端内的逻辑服务器(即单人游戏世界)中也可能为 false
。
每当您需要确定是否应运行游戏逻辑和其他机制时,请使用此检查。例如,如果您希望每次玩家点击您的方块时对玩家造成伤害,或者让您的机器将泥土加工成钻石,您应确保 #isClientSide
为 false
后再执行这些操作。将游戏逻辑应用于逻辑客户端可能会导致不同步(幽灵实体、不同步的统计数据等)或在最坏情况下导致崩溃。
提示:此检查应作为您的默认选择。每当您有 Level
可用时,请使用此检查。
FMLEnvironment.dist
FMLEnvironment.dist
是 Level#isClientSide()
检查的物理端对应项。如果此字段为 Dist.CLIENT
,则表示您在物理客户端上。如果该字段为 Dist.DEDICATED_SERVER
,则表示您在物理服务器上。
@Mod
在处理仅客户端类时,检查物理环境非常重要。推荐的方式是通过指定单独的 @Mod
注解来分离应仅在特定物理端执行的代码,将 dist
参数设置为应加载 Mod 类的物理端:
@Mod("examplemod")
public class ExampleMod {
public ExampleMod(IEventBus modBus) {
// 在此执行应在两端执行的逻辑
}
}
@Mod(value = "examplemod", dist = Dist.CLIENT)
public class ExampleModClient {
public ExampleModClient(IEventBus modBus) {
// 在此执行应仅在物理客户端执行的逻辑
Minecraft.getInstance().whatever();
}
}
@Mod(value = "examplemod", dist = Dist.DEDICATED_SERVER)
public class ExampleModDedicatedServer {
public ExampleModDedicatedServer(IEventBus modBus) {
// 在此执行应仅在物理服务器执行的逻辑
}
}
提示:通常期望 Mod 在任一端都能工作。这尤其意味着,如果您正在开发仅客户端的 Mod,您应验证该 Mod 是否确实在物理客户端上运行,如果没有,则不执行任何操作。