This commit is contained in:
boybook
2025-12-01 20:59:16 +08:00
parent 12738a142c
commit 760c2dd9ad
5535 changed files with 21070 additions and 2021 deletions

View File

@@ -0,0 +1,242 @@
# Spigot插件编写
<iframe src="https://cc.163.com/act/m/daily/iframeplayer/?id=64818bb8c31a9c0f360dc5b2" width="800" height="600" allow="fullscreen"/>
## 语言基础
编写Spigot插件需要一定的Java语言基础并在后续开发中会使用到Maven或Gradle来构建Spigot插件在此仅提供部分链接供参考需要开发者自行学习。
- [Java](https://www.runoob.com/java/java-tutorial.html)
- [Maven](https://maven.apache.org/)
- [Gradle](https://gradle.org/)
在掌握Java语言基础后需要继续学习Spigot、BC的API并进行插件开发。同样也需要开发者自行进行学习。
- [Spigot插件开发教程](https://www.spigotmc.org/wiki/spigot-plugin-development/)
- [BC插件开发教程](https://www.spigotmc.org/wiki/bungeecord-plugin-development/)
## 开始编写Spigot插件
### 创建项目
> 本教程会使用IntelliJ IDEA来进行插件开发并使用Minecraft Development插件来快速创建项目。
>
> 如果没有安装的可以提前进行安装
>
> ![](./images/01.png)
首先点击创建项目找到Minecraft分类选择Spigot插件。
![](./images/02.png)
接着自行填写GroupId和ArtifactId点击下一步后进行如下更改
1. 选择Minecraft Version为1.12.2
2. 填写Depend为SpigotMaster
![](./images/03.png)
点击下一步后,选择目录和目录名进行项目创建。
### 添加依赖
在下载SpigotMaster插件后复制文件路径。将下方指令中的path-to-jar替换为路径x.x.x替换为版本号。
SpigotMaster插件和之前安装到服务器里的是同一个可以点[](https://mc.163.com/dev/mcmanual/mc-dev/mcguide/27-手机网络游戏/课程10使用Spigot开服/99-下载内容.html?catalog=1)查看下载方式。
```
mvn install:install-file -Dfile=path-to-jar -DgroupId=com.neteasemc -DartifactId=SpigotMaster -Dversion=x.x.x-SNAPSHOT -Dpackaging=jar
```
在IDEA的Maven指令中执行
![](./images/04.png)
执行成功后会看到BUILD SUCCESS的输出。
![](./images/05.png)
接下来在pom.xml中配置dependency添加依赖。
```
<dependency>
<groupId>com.neteasemc</groupId>
<artifactId>SpigotMaster</artifactId>
<version>1.3.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
```
其中版本处需要替换为自己安装的版本。添加完成后dependency如下。
![](./images/06.png)
### 代码编写
完成了依赖的添加就可以使用SpigotMaster来和中国版基岩版客户端进行通信了。
在SpigotMaster的接口中涉及到与客户端通信的方法主要有两个。
- listenForEvent
- notifyToClient
使用SpigotMaster实例下的这两个方法将可以实现绝大多数通信的需求。
其他方法可以参考:[SpigotMaster文档](https://mc.163.com/dev/mcmanual/mc-dev/mcguide/27-手机网络游戏/课程10使用Spigot开服/81-SpigotMasterAPI文档.html?catalog=1)
SpigotMaster的实例可以通过下方代码获取。
```java
public void onEnable() {
SpigotMaster spigotMaster = (SpigotMaster) Bukkit.getPluginManager().getPlugin("SpigotMaster");
}
```
#### listenForEvent
使用该方法可以监听客户端发送来的事件,需要提供参数:
- `namespace` - 来源客户端系统的namespace
- `system` - 来源客户端系统的systemName
- `event` - 事件名
- `handler` - 回调函数
在这里我们先暂时编写一个简单的监听事件的函数。
```java
package me.zhanshi123.apollo2example;
import com.neteasemc.spigotmaster.SpigotMaster;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public final class Apollo2Example extends JavaPlugin {
private static Apollo2Example instance;
public static Apollo2Example getInstance() {
return instance;
}
private SpigotMaster spigotMaster;
public SpigotMaster getSpigotMaster() {
return spigotMaster;
}
@Override
public void onEnable() {
instance=this;
spigotMaster = (SpigotMaster) Bukkit.getPluginManager().getPlugin("SpigotMaster");
spigotMaster.listenForEvent("testMod", "testModBeh", "TestEvent", (player, map) -> {
getLogger().info(player.getName());
map.forEach((key, value) -> getLogger().info("k: " + key + "v: " + value));
});
}
@Override
public void onDisable() {
// Plugin shutdown logic
}
}
```
调用这个方法将会注册一个命名空间为testMod系统名为testModBeh的TestEvent事件并在收到数据时打印发送的玩家名和事件的信息字典。
Java的listenForEvent的回调函数会在Python端调用NotifyToServer时触发。对应的代码如下
```python
self.NotifyToServer("TestEvent", {"data": "测试数据"})
```
在**ClientSystem**内调用**NotifyToServer**方法传递事件和对应参数。完整的Python代码见下一节。
#### notifyToClient
该方法可以主动给客户端发送事件,需要提供参数:
- `player` - 接收事件的玩家
- `namespace` - 在客户端系统使用ListenForEvent监听的namespace
- `system` - 在客户端系统使用ListenForEvent监听的systemName
- `event` - 事件名
- `data` - 事件参数。注意,要使用-2指代本地玩家的entityId。
我们同样编写一个指令用来后续测试Python端是否能正常收到消息
首先在plugin.yml中注册指令
```yaml
commands:
apollotest:
```
接下来新建一个类实现CommandExecutor接口在玩家执行指令时调用SpigotMaster实例并notifyToClient
```java
package me.zhanshi123.apollo2example;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.Map;
public class Commands implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
return true;
}
Player player = (Player) sender;
Map<String, Object> data = new HashMap<>();
data.put("msg", "这是一条来自Java服的消息");
Apollo2Example.getInstance().getSpigotMaster()
.notifyToClient(player, "testMod", "testModDev", "TestServerEvent", data);
player.sendMessage("notifyToClient已执行");
return true;
}
}
```
在这里我们将作为testMod这个命名空间的testModDev的系统发送事件事件名为TestServerEvent数据为一个Map它会在客户端被读取为Python的字典类型进行处理其中的数据也会被转换为Python类型
| Java类型 | Python类型 |
| ------------------------ | ---------- |
| null | None |
| boolean | bool |
| int | int |
| long | long |
| BigInteger(2^63到2^64-1) | long |
| float | float |
| double | float |
| String | str |
| List\<Object\> | list |
| Map<String, Object> | dict |
对于Python端需要接受来自Java端的notifyToClient发送的数据需要调用**ClientSystem**的**ListenForEvent**方法,对应的代码如下:
这样就会在收到事件时打印参数。完整的Python代码见下一节。
```python
def __init__(self, namespace, systemName):
ClientSystem.__init__(self, namespace, systemName)
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)
```
接下来在onEnable方法中进行注册。
```java
Bukkit.getPluginCommand("apollotest").setExecutor(new Commands());
```
随后执行`mvn package`即可对插件进行打包随后可以上传至小小云对应Spigot端的plugins文件夹。
然后重启服务器,即可让插件加载。

View File

@@ -0,0 +1,188 @@
# 客户端模组编写
本节将主要介绍如何制作客户端模组与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-12^31到2^63-1 | Long |
| int/long2^63到2^64-1 | BigInteger |
| float | Double |
| str | String |
| list | List\<Object\> |
| dictkey必须为str | Map<String, Object> |
### 监听服务器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解码是我们传输的消息

View File

@@ -0,0 +1,159 @@
# 作业
<iframe src="https://cc.163.com/act/m/daily/iframeplayer/?id=64818c70c31a9c0f360dc5c0" width="800" height="600" allow="fullscreen"/>
学习了服务端与客户端之间的基本通信方法之后我们可以尝试来完成一个使用模组SDK来实现的全息字的功能。
## 要求
在玩家加入游戏之后,在玩家客户端的指定位置生成一个文字面板(全息字)。
整个通信流程应该如下图所示:
```mermaid
sequenceDiagram
客户端->>服务端: 客户端加载完毕
服务端-->>客户端: 生成文字面板的参数
客户端->>服务端: 生成文字面板,返回生成是否成功
```
> **为什么需要在客户端加载完毕的时候主动通知服务端而不是直接监听服务端的PlayerJoinEvent**
>
> 因为在PlayerJoinEvent触发的时候基岩版客户端可能还没有完全加载完毕。
>
> 在这个时候给客户端发送事件,有可能客户端模组还没有初始化完成,无法处理请求。
## 实现过程
### Spigot插件
首先新建Spigot项目操作步骤和之前一致。这里新建了一个名为TutorialHologram的项目并配置pom.xml添加SpigotMaster插件的maven依赖。
在开始编写插件之前,我们预先定义好,客户端的命名空间为`testHologram`,因此,根据[开发规范](https://mc.163.com/dev/mcmanual/mc-dev/mcguide/27-%E6%89%8B%E6%9C%BA%E7%BD%91%E7%BB%9C%E6%B8%B8%E6%88%8F/%E8%AF%BE%E7%A8%8B5%EF%BC%9A%E6%8F%92%E4%BB%B6%E6%95%99%E5%AD%A6/%E7%AC%AC1%E8%8A%82%EF%BC%9A%E5%AE%98%E7%BD%91%E6%8F%92%E4%BB%B6%E8%A7%84%E8%8C%83.html?catalog=1),客户端系统名应为`testHologramBeh`,服务端系统名应为`testHologramDev`
- 客户端通知加载完毕的事件定义为`ClientLoadFinishEvent`
- 传输生成文字面板的参数的事件定义为`HologramParameterEvent`
- 返回生成结果的事件定义为`HologramGeneratedEvent`
推荐将这部分命名空间和事件名定义为常量。
> 教程为了方便将所有代码都写入主类,在实际开发过程中,不推荐将所有代码写入一个类中!!
```java
private final String NAMESPACE = "testHologram";
private final String CLIENT_SYSTEM_NAME = "testHologramBeh";
private final String SERVER_SYSTEM_NAME = "testHologramDev";
private final String CLIENT_LOAD_FINISH_EVENT = "ClientLoadFinishEvent";
private final String HOLOGRAM_PARAMETER_EVENT = "HologramParameterEvent";
private final String HOLOGRAM_GENERATED_EVENT = "HologramGeneratedEvent";
```
首先,生成一个文字面板,需要提供这个文字面板的坐标,文本的信息,我们在作业中可以简单地硬编码坐标的位置和文本的内容。例如坐标为`(0,100,0)`,内容为`这是一个文字面板`
在监听ClientLoadFinishEvent后发送文字面板参数信息。同时监听HologramGeneratedEvent输出结果。
```java
@Override
public void onEnable() {
spigotMaster = (SpigotMaster) Bukkit.getPluginManager().getPlugin("SpigotMaster");
spigotMaster.listenForEvent(NAMESPACE, CLIENT_SYSTEM_NAME, CLIENT_LOAD_FINISH_EVENT, (player, map) -> {
Map<String, Object> data = new HashMap<>();
data.put("x", 0);
data.put("y", 100);
data.put("z", 0);
data.put("text", "这是一个文字面板");
spigotMaster.notifyToClient(player, NAMESPACE, SERVER_SYSTEM_NAME, HOLOGRAM_PARAMETER_EVENT, data);
});
spigotMaster.listenForEvent(NAMESPACE, CLIENT_SYSTEM_NAME, HOLOGRAM_GENERATED_EVENT, (player, map) -> {
boolean success = (boolean) map.get("suc");
getLogger().info("生成全息字 " + player.getName() + " " + success);
});
}
```
在HologramGeneratedEvent中我们可以监听来自客户端的事件并从中获取suc的值来判断生成是否成功可以继续拓展插件的功能。
这样我们的Spigot插件部分的代码就编写完成了可以构建后装入服务器。
### 客户端模组
和之前的操作一样新建一个插件团队名称填写test模组名填写hologram勾选游戏服和大厅服。
生成完成后打开插件文件夹,删除`developer_mods`文件夹里的内容,并将整个插件文件夹复制到部署设置的模组目录中。
#### 文字面板的生成
文字面板的API文档 [点我](https://mc.163.com/dev/mcmanual/mc-dev/mcdocs/1-ModAPI/%E6%8E%A5%E5%8F%A3/%E7%89%B9%E6%95%88/%E6%96%87%E5%AD%97%E9%9D%A2%E6%9D%BF.html?catalog=1)
通过文档的查阅,我们需要先后
1. 创建文字面板
2. 设置文字面板的位置
3. 如有需要的话 返回服务器文字面板的ID
#### 常量定义
之前在Spigot服插件编写的过程中已经定义了一些事件名常量方便起见我们也需要在客户端模组的常量文件中定义相同的常量。
`hologramConst.py`文件中定义下列变量
```python
ClientLoadFinishEvent = "ClientLoadFinishEvent"
HologramParameterEvent = "HologramParameterEvent"
HologramGeneratedEvent = "HologramGeneratedEvent"
```
#### 功能实现
首先在OnUiInitFinished函数中向服务端系统发送事件。
```python
# UI加载完成
def OnUiInitFinished(self, args):
logger.info("%s OnUiInitFinished", HologramConst.ClientSystemName)
self.NotifyToServer(HologramConst.ClientLoadFinishEvent, {})
```
接下来监听来自服务端系统的`HologramParameterEvent`事件,并设置回调函数。获取坐标,创建文字面板。
部分代码如下
```python
def __init__(self, namespace, systemName):
ClientSystem.__init__(self, namespace, systemName)
self.mUIMgr = uiMgr.UIMgr()
self.mTextBoardComp = clientApi.GetEngineCompFactory().CreateTextBoard(clientApi.GetLevelId())
self.ListenForEvent(HologramConst.ModName, HologramConst.ServerSystemName, HologramConst.HologramParameterEvent, self, self.OnHologramParameter)
self.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), HologramConst.UiInitFinishedEvent, self, self.OnUiInitFinished)
def OnHologramParameter(self, args):
x = args["x"]
y = args["y"]
z = args["z"]
text = args["text"]
boardId = self.mTextBoardComp.CreateTextBoardInWorld(text, (1, 1, 1, 1), (0.5, 0.5, 0.5, 0.1), True)
if not boardId:
self.NotifyToServer(HologramConst.HologramGeneratedEvent, {"suc": False})
return
self.mTextBoardComp.SetBoardPos(boardId, (x, y, z))
self.NotifyToServer(HologramConst.HologramGeneratedEvent, {"suc": True, "boardId": boardId})
def Destroy(self):
self.UnListenForEvent(HologramConst.ModName, HologramConst.ServerSystemName, HologramConst.HologramParameterEvent, self, self.OnHologramParameter)
self.UnListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), HologramConst.UiInitFinishedEvent, self, self.OnUiInitFinished)
if self.mUIMgr:
self.mUIMgr.Destroy()
```
OnHologramParameter函数会解析来自服务端的数据在指定xyz坐标创建文字面板如果失败返回信息中suc是False。如果成功suc为True并附带文字面板的id。
## 部署测试
前往服务器配置协议服勾选刚刚编写的testHologram模组重新部署后进入游戏进行测试。
![](./images/16.png)
进入游戏后,传送到坐标(0,100,0)附近,可以看到我们生成的文字面板。
![](./images/17.png)