# 客户端模组编写 本节将主要介绍如何制作客户端模组与Java服插件进行通信。 ## 语言基础 编写中国版基岩版客户端模组需要掌握Python2.7、模组SDK。 该部分较为基础,需要开发者自行安装并学习。推荐提前安装[补全库](https://mc.163.com/dev/mcmanual/mc-dev/mcguide/20-%E7%8E%A9%E6%B3%95%E5%BC%80%E5%8F%91/13-%E6%A8%A1%E7%BB%84SDK%E7%BC%96%E7%A8%8B/2-Python%E8%84%9A%E6%9C%AC%E5%BC%80%E5%8F%91/0-%E8%84%9A%E6%9C%AC%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8.html?key=%E8%A1%A5%E5%85%A8&docindex=1&type=0#%E5%AE%89%E8%A3%85mod-sdk%E8%A1%A5%E5%85%A8%E5%BA%93)。 - [Python2.7](https://www.python.org/downloads/release/python-2718/) - [模组SDK](https://mc.163.com/dev/mcmanual/mc-dev/mcguide/20-%E7%8E%A9%E6%B3%95%E5%BC%80%E5%8F%91/13-%E6%A8%A1%E7%BB%84SDK%E7%BC%96%E7%A8%8B/1-Mod%E5%BC%80%E5%8F%91%E7%AE%80%E4%BB%8B/1-Mod%E7%AE%80%E4%BB%8B.html?catalog=1) ## 项目创建 在开始代码编写之前,首先需要创建项目。 切换到插件标签页,点击新建插件。 ![](./images/07.png) 然后我们按照团队名,插件名称来填写信息,并且勾选大厅服/游戏服。 因为我们在之前已经在Spigot插件中定义好了插件的命名空间和系统名,所以我们这里按照下方截图填写,方便后面直接和Java服通信。 ![](./images/08.png) 创建完成后,对插件右键,打开目录。就可以看到插件的目录结构。 - behavior_packs - 行为包目录 - developer_mods - 在开服工具2.0中无用 - resource_packs - 资源包目录 - worlds - 存档(在开服工具2.0中仅用来配置行为包和资源包) 在这里,我们主要需要编写的地方就是**行为包目录、资源包目录**。 - **行为包**主要用来存放客户端模组的代码、物品定义、实体定义等等。 - **资源包**主要用来存放客户端模组的美术资源,文本资源等等。 因为developer_mods在开服工具2.0中没有用途,所以我们可以打开文件夹,将截图所示内容删除。 ![](./images/09.png) 完成删除后,我们可以将整个testMod文件夹剪切到服务器配置中的Mod目录文件夹。 ![](./images/12.png) 接下来,打开装有Python插件的IDEA或者PyCharm,对客户端模组进行脚本编辑。 在File->Open中复制文件路径,打开这个模组文件夹。 ![](./images/10.png) 然后对`testModBehavior`右键,将其标记为`Sources Root`,这样补全库才能正常工作。 ![](./images/11.png) 接下来,我们可以打开`modConst.py`,在这里可以看到这个模组的一些常量。 - `ModName` 代表 模组命名空间 - `ClientSystemName` 代表 模组客户端系统名 可以回顾一下Java服插件中的命名空间和客户端系统名,可以看到这它们是一一对应的。 **只有在服务器和客户端通信时使用相同命名空间和系统名,通信数据才会被成功处理。** ```python # -*- coding: utf-8 -*- # 整个Mod的一些绑定配置 ModVersion = "1.0.0" ModName = "testMod" ClientSystemName = "testModBeh" ClientSystemClsPath = "testModScript.modClientSystem.ModClientSystem" ServerSystemName = "testModDev" ServerSystemClsPath = "testModScript.modServerSystem.ModServerSystem" # 引擎事件 UiInitFinishedEvent = "UiInitFinished" ``` 接下来打开`modClientSystem.py` ```python import client.extraClientApi as clientApi ``` 将文件顶部的代码修改为,方便正常使用补全库。 ```python import mod.client.extraClientApi as clientApi ``` ## 代码编写 功能需求: - 在玩家客户端UI初始化完成时,向服务器发送TestEvent事件,参数任意。 - 监听服务器TestServerEvent,并打印信息到控制台。 会用到以下两个函数: - [NotifyToServer](https://mc.163.com/dev/mcmanual/mc-dev/mcdocs/1-ModAPI/%E6%8E%A5%E5%8F%A3/%E9%80%9A%E7%94%A8/%E4%BA%8B%E4%BB%B6.html?key=NotifyToServer&docindex=1&type=0) - [ListenForEvent](https://mc.163.com/dev/mcmanual/mc-dev/mcdocs/1-ModAPI/%E6%8E%A5%E5%8F%A3/%E9%80%9A%E7%94%A8/%E4%BA%8B%E4%BB%B6.html?key=ListenForEvent&docindex=5&type=0) 除此之外,还有更多的事件相关的接口,可以参考[官方文档](https://mc.163.com/dev/mcmanual/mc-dev/mcdocs/1-ModAPI/%E6%8E%A5%E5%8F%A3/%E9%80%9A%E7%94%A8/%E4%BA%8B%E4%BB%B6.html?catalog=1)。 ### 向服务器发送TestEvent事件 在创建项目后的模板中,已经生成了监听UI初始化完成的事件,我们可以直接在这个事件的回调函数中向服务器通信。 直接使用NotifyToServer函数即可。发送的数据是一个Python字典。 ```python # UI加载完成 def OnUiInitFinished(self, args): logger.info("%s OnUiInitFinished", ModConst.ClientSystemName) self.NotifyToServer("TestEvent", {"data": "测试数据"}) ``` Python的类型会被转换成Java的类型,对照表如下: | Python类型 | Java类型 | | ---------------------------------------- | ------------------- | | None | null | | bool | Boolean | | int/long(-2^31到2^31-1) | Integer | | int/long(-2^63到-2^31-1,2^31到2^63-1) | Long | | int/long(2^63到2^64-1) | BigInteger | | float | Double | | str | String | | list | List\ | | dict(key必须为str) | Map | ### 监听服务器TestServerEvent事件 我们可以在客户端系统初始化时,监听这个事件并注册回调函数。在Destroy时注销监听。 ```python def __init__(self, namespace, systemName): ClientSystem.__init__(self, namespace, systemName) self.mUIMgr = uiMgr.UIMgr() self.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), ModConst.UiInitFinishedEvent, self, self.OnUiInitFinished) self.ListenForEvent(ModConst.ModName, ModConst.ServerSystemName, "TestServerEvent", self, self.OnServerEvent) def OnServerEvent(self, args): print "OnServerEvent", args def Destroy(self): self.UnListenForEvent(ModConst.ModName, ModConst.ServerSystemName, "TestServerEvent", self, self.OnServerEvent) self.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), ModConst.UiInitFinishedEvent, self, self.OnUiInitFinished) if self.mUIMgr: self.mUIMgr.Destroy() ``` 在这里,我们监听的命名空间,引用了ModConst中的ModName,对应Spigot插件中的命名空间`testMod`。还引用了ModConst中的ServerSystemName,对应Spigot插件中的系统名`testModDev`。因此这个监听函数将会正常监听来自服务器的信息。 ## 部署测试 至此我们完成了客户端与服务端之间双端通信的最基础的实现。接下来将客户端模组进行部署,进入服务器测试。 找到服务器配置->游戏配置->协议服,勾选testMod。进行部署。 ![](./images/13.png) 随后点击启动测试,进入游戏。并输入指令/apollotest 可以看到服务器控制台正常输出 ![](./images/14.png) 客户端控制台也正常输出。 ![](./images/15.png) Python命令行执行: ```python "OnServerEvent {'msg': '\xe8\xbf\x99\xe6\x98\xaf\xe4\xb8\x80\xe6\x9d\xa1\xe6\x9d\xa5\xe8\x87\xaaJava\xe6\x9c\x8d\xe7\x9a\x84\xe6\xb6\x88\xe6\x81\xaf'}".decode("utf-8") ``` ```python u"OnServerEvent {'msg': '\u8fd9\u662f\u4e00\u6761\u6765\u81eaJava\u670d\u7684\u6d88\u606f'}" ``` 消息经过utf8解码,是我们传输的消息