【干货】《我的世界》Mod开发教程13:网络


3楼猫 发布时间:2024-12-20 04:42:41 作者:甄别 Language

【干货】《我的世界》Mod开发教程13:网络-第0张

本期教程我们将探讨网络通信。网络用于服务器与客户端之间的数据同步。有时,我们无法直接从游戏中获取数据,比如上次提到的覆盖层(overlay)。覆盖层的参数没有直接获取游戏内相关信息的接口。这些数据与游戏逻辑无关,因为覆盖层类是游戏客户端的属性,数据存储在服务器端,因此无法直接获取。

上次我们想到了一个解决办法,即使用静态变量保存数据,在数据发生更改时更新。虽然我没有详细描述,但提到了每次数据更改时都要更新。这样,我们可以直接使用静态变量,但会引发一个问题:如果连接的是服务器,数据仍存储在服务器上,而客户端不运行逻辑,因此数据始终为零。这导致覆盖层在服务器环境下始终显示为零。要解决这个问题,需要通过网络将服务器端数据同步到客户端。

【干货】《我的世界》Mod开发教程13:网络-第1张

在此之前,我们还注意到 Parchment MC 和 New Forge 需要更新。Parchment MC 的地图版本是 8 月 13 日版,New Forge 的版本是 47.1.70。我们可以顺便更新一下,操作起来不会太麻烦。将地图版本改为 8.13,将 Forge 版本改为 47.1.70,然后加载 GRADLE 进行更改,出现“build successful”提示后,更新完成。

更新完成后,我们来实现网络通信。首先创建一个软件包,然后创建一个 Java 类,命名为 Channel。稍后我们会继续编写该类。接着创建另一个软件包,命名为 packet.paci,其中创建一个 Java 类,命名为 FarmXP,用于同步服务端数据到客户端。

【干货】《我的世界》Mod开发教程13:网络-第2张

首先,在packet包下创建一个新的软件包,命名为"server to player"。然后将 FarmXPPacket.java 文件拖拽到该包中。

现在,我们需要自己实现一些方法来处理这个packet。首先,我们需要定义 packet 中要使用的参数。由于我们只需要一个参数,即农场经验,因此我们可以定义一个私有的 final int 变量,命名为 farmXP。

【干货】《我的世界》Mod开发教程13:网络-第3张

接下来,我们创建构造函数。这里我们需要两个方法,首先是解码器。我们可以直接编写一个构造函数,命名为 public FarmXPPacket,参数为一个 friendly bad Buff。这个 Buff 是 Minecraft 提供的一种专门用于网络传输的工具,里面包含了许多现成可用的方法。我们可以直接使用这些方法,例如,我们可以写 farmXP = Buff.re() 来读取数据。除了我们目前需要的 read 方法外,如果我们想传输更复杂的数据,Buff 中还有其他有用的方法,比如 readBlogPost 和 readNBT 等。但在这里,我们只需使用 read 方法即可。

接下来,我们需要一个编码器。我们编写一个名为 public void encode 的方法,参数同样是一个 friendly bad Buff,但这次我们需要将数据写入 Buff 中。我们使用 Buff.right 方法来写入 int 类型的 farmXP。

【干货】《我的世界》Mod开发教程13:网络-第3张

这两个方法编写完成后,我们还需要一个处理数据的方法。我们编写一个名为 public void handle 的方法,参数为一个 supplier,其泛型为 network event 的 context。在这个方法中,我们可以对数据包进行处理。首先,我们可以通过 context.getPlayer() 来获取玩家对象。然后,context 中还包含了一些其他有用的方法,比如 getPlayer()。但由于我们是从服务端发送数据包,因此服务端只有一个,所以我们不需要使用 getPlayer() 方法。

另外,还有一点需要注意的是,这个网络系统是在另一个线程上运行的。它和MINECRAFT客户端或逻辑并不在同一个线程上。是的,它是多线程的。因此,我们不能直接与游戏的某些信息进行交互。但是,许多人都有这样的需求。因此,我们提供了一个名为"in cool work"的解决方案。通过传入一个RUNNABLE,在下一个游戏主线程的时刻执行。

【干货】《我的世界》Mod开发教程13:网络-第3张

比如,"Player fx p provider"执行"点XPCLINT等于放XP",然后调用"context点set pack handled",最后直接返回true。因为在这里,我们没有复杂的逻辑,所以不需要判断操作是否成功。如果这种简单逻辑失败的可能性较小。当然,如果你的逻辑可能出错,就需要使用try-catch进行包裹,或者定义一些判断条件。最后,如果失败,就返回false。handle部分就完成了。

现在,我们需要在channel这一部分进行操作。我们可以看到文档里提供了许多关于network的内容。我们直接复制这些内容,并导入。对于"resource location",我们直接使用我们自己的mod地。因此,我们将其命名为"FMXP"。接下来,我们使用public static void register方法。我们需要注册我们的频道,所以我们必须对频道进行初始化,包括注册我们的packet。我们使用instance来注册消息。

【干货】《我的世界》Mod开发教程13:网络-第3张

这里有一些参数,比如Message、Encoder、Decoder、Consumer和network direction。但是,这种写法可读性较差,有时候会忘记参数的顺序。因此,我们可以使用另一种方法,即使用message builder。使用message builder可以增强可读性。首先,第一个参数是我们的firm xp packet的class。然后,我们在这里定义一个id,让其初始值为零。每次使用时,id都会自增,这样就不会重复。

第三个参数是网络方向,lay to clint。然后是decoder,即我们之前写的构造函数。我们将其实例化为cos函数。编码器是我们之前编写的。最后,点consumer,这里使用了之前讲过的"my sweet network sweet"。还有一些自动生成的方法可供使用,比如method network three和farm x p take it handle。最后,点击ADD即可完成注册。

【干货】《我的世界》Mod开发教程13:网络-第3张

下面是我们自定义的方法。在这里,我们继续编写public static void sendToPlayer方法。关于参数,我们可以看到这个instance,在这个方法中我们可以发送任何内容。因此,我们可以直接仿照这个方法编写。这个方法中的message是在这个位置定义的,所以我们也在这里定义一个message参数。接着我们需要另一个参数,即要发送给谁。

【干货】《我的世界》Mod开发教程13:网络-第3张

因此我们需要添加一个player参数。在这里,我们可以根据文档上的packet Distribute,点选player,然后将message发送给对应的玩家。对于farm xp overlay,我们可以在这里添加player、FarmXPProvider,然后点选XPCLIENT,将文字改为farm xp:。回到我们的farmXP类,我们需要在XP发生更改时更新这个数值。我们可以找一个合适的时机,在这个时机同步这个数据。如果找不到我们的这个event listener,我们可以在使用XP的时候进行同步。因为在使用之后就会发生更改。

在这里,我们可以编写channel.sendToPlayer(newBMXPitXP, player),然后从event中获取玩家并转换成服务器玩家。当然,在这里可能会遇到一些潜在的问题。同时,player clone也需要同步。我们可以直接复制这个内容。同样地,在玩家加入服务器时也需要同步这个数据。可以使用PlayerLoginEvent,我们之前应该已经写过了。

【干货】《我的世界》Mod开发教程13:网络-第3张

完成了这些工作之后,我们需要将其注册进去。在FMLCommonSetupEvent中,调用channel.register方法注册。完成这一系列步骤后,我们进行测试。由于这次测试涉及到网络,所以我们需要运行一个服务器。服务器启动完成后,我们运行lint,然后在多人游戏中连接到本地主机。现在我们的XP是零。点击右键后,程序崩溃了。但不用担心,这是意料之中的。

【干货】《我的世界》Mod开发教程13:网络-第3张

在这一段中,我们要探讨一个重要的知识点,即如何根据错误报告来识别问题。当我们在处理代码时,有时会遇到类似于 "net.minecraft"、"clean"、"player"、"local there" 这样的提示信息。这些信息表明在客户端和服务端都存在一些差异。在客户端,"player" 实际上是指本地玩家,而在服务端,它指的是服务器上的玩家。因此,有些代码只能在服务端执行,而在客户端则不应该执行。如果错误的代码在客户端运行,程序就会崩溃。我们可以通过一些方法来确定当前是在哪个端,比如使用 "level.isClientSide" 或者 "Dist.isClient" 这样的方法。

【干货】《我的世界》Mod开发教程13:网络-第3张

一般来说,我们优先使用前两种方法,而最后一种方法只是在前两种方法无法使用时才考虑。在代码中,我们可以这样写:如果事件发生在客户端,那么就不执行特定的代码,只有在服务端才执行。同样的逻辑也适用于其他情况。通过这样的方式,我们可以确保代码在不同端的正确执行。在本段的最后,我们还介绍了如何在获取经验时同步数据,以及如何在客户端和服务端之间发送数据的方法。那么本期教程就到此结束。


© 2022 3楼猫 下载APP 站点地图 广告合作:asmrly666@gmail.com