Files
boybook 760c2dd9ad 2.6
2025-12-01 20:59:16 +08:00

230 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 作业
<iframe src="https://cc.163.com/act/m/daily/iframeplayer/?id=64818d47c31a9c0f360dc5da" width="800" height="600" allow="fullscreen"/>
要求依赖客户端Mod制作一个简易的菜单插件。
- 进入游戏自动生成一个在Hud界面上的菜单
- 服务器可以配置每个菜单按钮:
- 索引位置
- 图片、文本
- 点击后以玩家身份执行指令
示例图:
![](./images/18.png)
## Spigot插件
Spigot插件的逻辑较为简单在玩家UI加载完成之后向玩家客户端发送提前加载好的菜单配置文件即可。
配置文件格式:
```yaml
button1:
idx: 1
# 该按钮在菜单中的位置
texture: 'textures/items/apple'
# 该按钮显示的贴图
text: '按钮1'
# 该按钮下方的提示文本
commands:
- 'say 点击了按钮1'
# 点击后执行的玩家指令
button2:
idx: 2
texture: 'textures/items/diamond_sword'
text: '按钮2'
commands:
- 'say 点击了按钮2'
```
读取配置文件的代码不过多介绍,需要参考可以下载源码进行查看。
主类中包含了与客户端通讯的关键代码,供参考:
```java
package me.zhanshi123.tutorialmenu;
import com.neteasemc.spigotmaster.SpigotMaster;
import me.zhanshi123.tutorialmenu.config.ConfigManager;
import me.zhanshi123.tutorialmenu.config.MenuButtonConfig;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public final class TutorialMenu extends JavaPlugin {
private static TutorialMenu instance;
private SpigotMaster spigotMaster;
private final String NAMESPACE = "testMenu";
private final String SERVER_SYSTEM_NAME = "testMenuDev";
private final String CLIENT_SYSTEM_NAME = "testMenuBeh";
private final String CLIENT_UI_LOADED_EVENT = "ClientUiLoadedEvent";
private final String SERVER_MENU_EVENT = "ServerMenuEvent";
private final String CLIENT_MENU_CLICKED_EVENT = "ClientMenuClickedEvent";
public static TutorialMenu getInstance() {
return instance;
}
@Override
public void onEnable() {
instance = this;
ConfigManager.getInstance().loadConfig();
spigotMaster = (SpigotMaster) Bukkit.getPluginManager().getPlugin("SpigotMaster");
spigotMaster.listenForEvent(NAMESPACE, CLIENT_SYSTEM_NAME, CLIENT_UI_LOADED_EVENT, (player, map) ->
spigotMaster.notifyToClient(player, NAMESPACE, SERVER_SYSTEM_NAME, SERVER_MENU_EVENT, ConfigManager.getInstance().getClientData()));
spigotMaster.listenForEvent(NAMESPACE, CLIENT_SYSTEM_NAME, CLIENT_MENU_CLICKED_EVENT, (player, map) -> {
int index = (int) map.get("index");
MenuButtonConfig menuButtonConfig = ConfigManager.getInstance().getMenuConfigs().get(index);
if (menuButtonConfig == null) {
getLogger().warning("玩家 " + player.getName() + " 发送了一个不正确的菜单数据");
return;
}
menuButtonConfig.dispatchCommand(player);
});
}
@Override
public void onDisable() {
}
}
```
## 客户端模组
创建一个命名空间为testMenu的插件删除`developer_mods`文件夹之后,就可以先创建一个空白附加包,开始编辑菜单界面。
### 界面编辑
对于这种有序排列的界面,我们可以直接使用布局面板来自动给按钮排版。
1. 新建一个布局面板,将其锚点都设置在左上方,并设置`尺寸X`为适应,修改排列方式为水平排布。
2. 新建一个面板,命名为`btn_tpl`作为按钮的模板。自行调整其尺寸教程中设置为60x60。
3.`btn_tpl`下新建一个按钮这就是按钮本体。自行调整其尺寸教程中设置为40x40。
4. 在空间结构中展开界面`button`,找到`button_label`,它被用来显示按钮上的文本,将它的父锚点设置到下,子锚点设到上。这样它就会整个显示在按钮图片的下方。
![](./images/16.png)
5. 最后将`btn_tpl`设置为隐藏。后面我们会把它作为模板来克隆别的按钮,添加到`stack_panel`中。
![](./images/17.png)
> 除了使用布局面板+克隆模板的方式来制作这种多按钮的界面。
>
> 还可以使用网格来实现。需要注意的是网格的内容在Create生命周期的时候有可能还没有被生成。
>
> 需要监听[GridComponentSizeChangedClientEvent](https://mc.163.com/dev/mcmanual/mc-dev/mcdocs/1-ModAPI/%E4%BA%8B%E4%BB%B6/UI.html#gridcomponentsizechangedclientevent)在其数量由0变为其他的时候才能对网格下的控件进行操作。
### 界面逻辑
将刚刚编辑好的文件,复制到对应的客户端资源`testMenu/resource_packs/testMenuResource/ui`处。
接下来编辑UiDef修改screen的值为刚刚编辑的json文件。
```python
UIData = {
UIDef.MenuScreen: {
"cls": "testMenuScript.ui.test_menu_screen.MenuScreen",
"screen": "test_menu.main",
"isHud": 1
}
}
```
客户端界面初始化完成后,加载`UiMgr`,并向服务器通知。
```python
def OnUiInitFinished(self, args):
logger.info("%s OnUiInitFinished", MenuConst.ClientSystemName)
self.mUIMgr.Init(self)
self.NotifyToServer("ClientUiLoadedEvent", {})
```
监听来自服务器的`ServerMenuEvent`事件,并通过`mUIMgr`获取`ScreenNode`实例,调用`SetData`方法来设置来自服务端的数据。
```python
def __init__(self, namespace, systemName):
ClientSystem.__init__(self, namespace, systemName)
self.mUIMgr = uiMgr.UIMgr()
self.ListenForEvent(clientApi.GetEngineNamespace(), clientApi.GetEngineSystemName(), MenuConst.UiInitFinishedEvent, self, self.OnUiInitFinished)
self.ListenForEvent(MenuConst.ModName, MenuConst.ServerSystemName, "ServerMenuEvent", self, self.OnServerMenu)
def OnServerMenu(self, args):
print "OnServerMenu", args
if self.mUIMgr:
self.mUIMgr.GetUI(UIDef.MenuScreen).SetData(args["data"])
else:
print "UIMgr还没有加载!"
```
编写MenuScreen的`SetData`方法,通过传入的数据来克隆按钮。并给按钮添加回调。
[Clone](https://mc.163.com/dev/mcmanual/mc-dev/mcdocs/1-ModAPI/%E6%8E%A5%E5%8F%A3/%E8%87%AA%E5%AE%9A%E4%B9%89UI/UI%E7%95%8C%E9%9D%A2.html?key=Clone&docindex=2&type=0#clone)具体参数见文档。
```python
# -*- coding: utf-8 -*-
import mod.client.extraClientApi as clientApi
from testMenuScript.menuConst import ModName, ClientSystemName
ScreenNode = clientApi.GetScreenNodeCls()
class MenuScreen(ScreenNode):
"""
Menu
"""
def __init__(self, namespace, name, param):
ScreenNode.__init__(self, namespace, name, param)
self.mStackPanel = "/stack_panel"
self.mTemplate = "/btn_tpl"
print '==== %s ====' % 'init MenuScreen'
# Create函数是继承自ScreenNode会在UI创建完成后被调用
def Create(self):
print '==== %s ====' % 'MenuScreen Create'
def OnBtnClick(self, args):
print args["ButtonPath"]
path = args["ButtonPath"] # 路径为 /stack_panel/btn_x/button
buttonId = int(path[path.rindex("_") + 1:path.index("/button")]) # 截取路径中的x它就是按钮的索引
clientApi.GetSystem(ModName, ClientSystemName).NotifyToServer("ClientMenuClickedEvent", {"index": buttonId}) # 发送给服务器
def SetData(self, data):
data = sorted(data, key=lambda x: x["index"])
# 对按钮顺序进行排序
for btn in data:
newName = "btn_{}".format(btn["index"]) # 把按钮对应的index存在路径中后面按钮点击时根据路径判断是哪个按钮
self.Clone(self.mTemplate, self.mStackPanel, newName)
newPath = self.mStackPanel + "/" + newName
self.GetBaseUIControl(newPath).SetVisible(True) # 设置可见
newPath += "/button"
btnControl = self.GetBaseUIControl(newPath).asButton()
btnControl.AddTouchEventParams({"isSwallow": True}) # 先开启按钮回调功能
btnControl.SetButtonTouchUpCallback(self.OnBtnClick) # 再设置按钮回调函数
# 按钮的贴图有3个分别对应默认、按下、悬浮。这里三个都设置。
self.GetBaseUIControl(newPath + "/default").asImage().SetSprite(btn["texture"])
self.GetBaseUIControl(newPath + "/pressed").asImage().SetSprite(btn["texture"])
self.GetBaseUIControl(newPath + "/hover").asImage().SetSprite(btn["texture"])
self.GetBaseUIControl(newPath + "/button_label").asLabel().SetText(btn["text"]) # 设置按钮下的文本
```
## 效果展示
![](./images/19.png)
## 代码下载
Spigot插件[点我](https://g79.gdl.netease.com/TutorialMenu-Spigot.zip)
客户端模组:[点我](https://g79.gdl.netease.com/testMenu-Python.zip)