MCBBS Wiki欢迎您共同参与编辑!在参与编辑之前请先阅读Wiki方针。
如果在编辑的过程中遇到了什么问题,可以去讨论板提问。
为了您能够无阻碍地参与编辑 未验证/绑定过邮箱的用户,请尽快绑定/验证。
MCBBS Wiki GitHub群组已上线!
您可以在回声洞中发表吐槽!
服务器状态监控。点击进入
本站由MCBBS用户自行搭建,与MCBBS及东银河系漫游指南(北京)科技有限公司没有从属关系。点此了解 MCBBS Wiki 不是什么>>
FancyMenu Wiki/自定义元素:修订间差异
(创建页面,内容为“== 关于 == 你可以通过FancyMenu API为FancyMenu编写拓展模组实现自定义元素。 == 准备开发环境 == 请参阅 准备工作区。 == 添加新元素 == 每个自定义元素(或item,因为元素在内部被称为此)都需要封装到<code>CustomizationItemContainer</code>中。 在模组初始化时,这个容器需要注册到<code>CustomizationItemRegistry</code>。 === 创建Item === 实际上…”) |
小 (// Edit via Wikiplus) |
||
第557行: | 第557行: | ||
现在你可以使用你自己的布局元素了! |
现在你可以使用你自己的布局元素了! |
||
[[分类:FancyMenu]] |
2022年11月22日 (二) 15:34的最新版本
关于
你可以通过FancyMenu API为FancyMenu编写拓展模组实现自定义元素。
准备开发环境
请参阅 准备工作区。
添加新元素
每个自定义元素(或item,因为元素在内部被称为此)都需要封装到CustomizationItemContainer
中。
在模组初始化时,这个容器需要注册到CustomizationItemRegistry
。
创建Item
实际上item的类是CustomizationItem
。这个类是渲染它并存储其所有属性的项。
给你的项创建一个CustomizationItem
的新子类,自己的孩子好好起名嗷。
在这里,我会将我的演示用项类命名为ExampleCustomizationItem
。
在这个类中有两个很重要的东西。
构造方法(Constructor)
第一个就是这个类的构造方法。
在这里,你需要反序列化 已保存/序列化 的项实例的属性。并需要把它设置为新的真实例。
保存到布局时,项会被序列化为PropertiesSections
。
你只关心(你自己)你的自定义项的值就行。每一项的基本的长宽高坐标部分啥的都默认让超类整完了。
//The constuctor is used to de-serialize the PropertiesSection and set all of its values to the new real item instance.
//构造方法用于反序列化PropertiesSection并将其所有值设置为新的真项实例。
public ExampleCustomizationItem(CustomizationItemContainer parentContainer, PropertiesSection item) {
//The superclass will automatically get values like orientation, x pos, y pos, width and height and will set it to the real item instance.
//超类会自动获取长宽高位置坐标等将它们设置到真项实例中。
super(parentContainer, item);
//Getting a custom property from the serialized item instance and set it to the real instance
//从序列化的项实例中获取自定义属性并将其设置为真实例。
String someProperty = item.getEntryValue("saved_property");
if (someProperty != null) {
this.someField = someProperty;
}
}
render()
还有一件事,你得渲染你的项。
这个方法是你的项的灵魂,使用这个方法来改变你的项的可视部分,就那个一会你可以在菜单看见的。
所以就在这里做你的事,知道吗?
这里唯一真正必要的部分是将此方法中的所有内容封装到shouldRender()
中。
这个方法检查可视化需求和其他重要内容,所以请别把这里忘了捏。
@Override
public void render(PoseStack matrix, Screen menu) throws IOException {
//This is really important and should be in every item render method, to check for visibility requirements and more.
//为了检查可视化需求这应该在所有的项渲染方法中。
if (this.shouldRender()) {
//Always use getPosX() and getPosY() to get the X and Y positions of the item.
//就用getPosX()和getPosY()方法获取项的X、Y位置就行。
//The fields posX and posY aren't the final position, just the base pos without the orientation!
//这里的posX和posY字段不是指最终位置,而只是没有方向的基本pos!
int x = this.getPosX(menu);
int y = this.getPosY(menu);
//We want to use placeholder text values for our 'someField' string, so we use the DynamicValueHelper to convert them,
//我们想为我们的"someField"字符串使用占位符文本值,使用DynamicValueHelper来转换即可,
//but they should look like placeholders in the editor, so we only convert them when not in the editor.
//但在编辑器中它们应该看着像占位符,所以我们不在编辑器中时转换它们。
String text;
if (!isEditorActive()) {
text = DynamicValueHelper.convertFromRaw(this.someField);
} else {
text = StringUtils.convertFormatCodes(this.someField, "&", "§");
}
//Always try to make your items' opacity changeable by setting the 'opacity' field!
//你可以尝试设置"opacity"来改变项的不透明度。
//This field is set by the "delay appearance" feature to control the fade-in opacity.
//该字段由"delay appearance"功能设置以控制淡入不透明度。
drawString(matrix, Minecraft.getInstance().font, text, x + 10, y + 10, -1 | Mth.ceil(this.opacity * 255.0F) << 24);
}
}
完整示例
这里是完整的自定义项示例。
package de.keksuccino.fancymenu.api.item.example;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import de.keksuccino.fancymenu.api.item.CustomizationItem;
import de.keksuccino.fancymenu.api.item.CustomizationItemContainer;
import de.keksuccino.fancymenu.menu.fancy.DynamicValueHelper;
import de.keksuccino.konkrete.input.StringUtils;
import de.keksuccino.konkrete.properties.PropertiesSection;
import de.keksuccino.konkrete.rendering.RenderUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.util.Mth;
import java.awt.*;
import java.io.IOException;
public class ExampleCustomizationItem extends CustomizationItem {
public String displayText = "placeholder";
public String backgroundColorString = "#38ff38";
public Color backgroundColor = new Color(56, 255, 56);
//The constuctor is used to de-serialize the PropertiesSection and set all of its values to the new real item instance.
//构造方法用于反序列化PropertiesSection并将其所有值设置为新的真项实例。
public ExampleCustomizationItem(CustomizationItemContainer parentContainer, PropertiesSection item) {
//The superclass will automatically get values like orientation, x pos, y pos, width and height and will set it to the real item instance.
//超类会自动获取长宽高位置坐标等将它们设置到真项实例中。
super(parentContainer, item);
//Getting the background HEX color from the serialized item
//从序列化项中获取背景HEX颜色。
String backColorHex = item.getEntryValue("background_color");
if (backColorHex != null) {
Color c = RenderUtils.getColorFromHexString(backColorHex);
if (c != null) {
this.backgroundColor = c;
this.backgroundColorString = backColorHex;
}
}
//Getting the display text string from the serialized item
//从序列化项中获取显示文本字符串。
String dText = item.getEntryValue("display_text");
if (dText != null) {
this.displayText = dText;
}
}
@Override
public void render(PoseStack matrix, Screen menu) throws IOException {
//This is really important and should be in every item render method, to check for visibility requirements and more.
//为了检查可视化需求这应该在所有的项渲染方法中。
if (this.shouldRender()) {
//Always use getPosX() and getPosY() to get the X and Y positions of the item.
//就用getPosX()和getPosY()方法获取项的X、Y位置就行。
//The fields posX and posY aren't the final position, just the base pos without the orientation!
//这里的posX和posY字段不是指最终位置,而只是没有方向的基本pos!
int x = this.getPosX(menu);
int y = this.getPosY(menu);
RenderSystem.enableBlend();
//Rendering the background color as background of the item.
//将背景颜色渲染为项的背景。
fill(matrix, x, y, x + this.getWidth(), y + this.getHeight(), this.backgroundColor.getRGB() | Mth.ceil(this.opacity * 255.0F) << 24);
//Rendering the display text to the upper-left side of the item
//将显示文本渲染到项的左上角。
if (this.displayText != null) {
//We want to use placeholder text values for the display text, so we use the DynamicValueHelper to convert them,
//我们想使用占位符文本值作为显示文本,使用DynamicValueHelper来转换即可,
//but they should look like placeholders in the editor, so we only convert them when not in the editor.
//但在编辑器中它们应该看着像占位符,所以我们不在编辑器中时转换它们。
String text;
if (!isEditorActive()) {
text = DynamicValueHelper.convertFromRaw(this.displayText);
} else {
text = StringUtils.convertFormatCodes(this.displayText, "&", "§");
}
//The 'opacity' field is used to set the fade-in opacity of the item when the "delay appearance" option is enabled for it.
//'opacity'字段用于设置项在启用“delay appearance”选项时的淡入不透明度。
//Always try to make your items' opacity changeable by setting the 'opacity' field! (I also used it in the fill method for the background)
////你可以尝试设置"opacity"来改变项的不透明度。(我还在背景的填充方法中使用了它)
drawString(matrix, Minecraft.getInstance().font, text, x + 10, y + 10, -1 | Mth.ceil(this.opacity * 255.0F) << 24);
}
}
}
}
创建编辑器元素
LayoutEditorElements
包含在布局编辑器中对你的项重要的所有内容。
这包括与你的项相关的所有UI部分,例如说在编辑器中右键单击项时的右键菜单。
给你的项起个合适的名字并创建一个LayoutEditorElement
的新子类。
接下来,我会将我的样例编辑器元素类命名为ExampleLayoutEditorElement
。
在这个类中有两种重要方法需要你留意一下。
init()
第一个,由编辑器调用以初始化你的编辑器元素(主要用于初始化UI)。
每一个编辑器元素都有rightclickMenu
(右键菜单)字段。这是元素的右键右键菜单。
这里的右键菜单已包含所有的默认内容,比如设置方向、删除元素等。
要是你想在这编辑你的项的自定义值,那需要在添加一个新的entries
到那个右键菜单并链接到你的项。
编辑器元素类的对象字段是你的实际CustomizationItem
实例,只需将其转换为你的项的子类(在本例中为ExampleCustomizationItem
)即可使用其自定义字段和方法。
@Override
public void init() {
//The superclass adds basic stuff to the right-click context menu, like visibility requirement controls, delete controls, orientation, etc.
//这里的超类为右键菜单添加了些基础功能,比如可视化需求控件,删除控件,方向等。
super.init();
//The 'object' field holds the CustomizationItem instance of this element.
//"object"字段包含这个元素的CustomizationItem实例。
//Cast it to your own item class, to get and set your own fields.
//把他转换成你自己的项类,以便于获取/设置你自己的字段。
ExampleCustomizationItem i = ((ExampleCustomizationItem)this.object);
//This button will be part of the right-click context menu of the element and is used to change the 'someField' field of the CustomizationItem subclass.
//这个按钮将会成为元素右键菜单的一部分,以便于更改CustomizationItem子类的'someField'字段。
AdvancedButton someFieldButton = new AdvancedButton(0, 0, 0, 0, "Set Some Field", (press) -> {
//This is the basic input popup for text content, used in many parts of FancyMenu.
//这是文本内容的基本输入弹出框,在FancyMenu的很多部分都有使用。
FMTextInputPopup pop = new FMTextInputPopup(new Color(0, 0, 0, 0), "Set Some Field Content", null, 240, (callback) -> {
//The callback of popups will be null, when pressing ESC in it to force-close it.
//当按下ESC强制关闭窗口时,窗口回调值为空。
if (callback != null) {
if (!callback.equals(i.someField)) {
//Create a snapshot before every change, so you can undo the change in the editor (using CTRL + Z)
//在每次更改前生成一个快照,顺便你也可以回退在编辑器中的更改(Ctrl+Z)
this.handler.history.saveSnapshot(this.handler.history.createSnapshot());
//Now set the new value to the item instance
//现在捏,给项实例设置新值。
i.someField = callback;
}
}
});
//Set the current value as default text of the text input popup
//设置现有的值为文本输入框的默认文本。
if (i.someField != null) {
pop.setText(i.someField);
}
//Open the popup
PopupHandler.displayPopup(pop);
});
someFieldButton.setDescription("This is just an example button tooltip.");
//Add the button to the right-click context menu content
//添加按钮到右键菜单内容中。
this.rightclickMenu.addContent(someFieldButton);
}
serializeItem()
简单但也重要,第二个重要方法在保存布局时被调用。
在这里,你的项实例(对象字段)被序列化为SimplePropertiesSection
以将其属性保存在布局文件中。
你只关心(你自己)你的自定义项的值就行。每一项的基本的长宽高坐标部分啥的都默认添加到了序列化实例中。
@Override
public SimplePropertiesSection serializeItem() {
ExampleCustomizationItem i = ((ExampleCustomizationItem)this.object);
SimplePropertiesSection sec = new SimplePropertiesSection();
//Add your custom item values here, so they get saved and can later be de-serialized again.
//在这添加你的自定义项的值,以至于它们被保存后可以再次反序列化。
sec.addEntry("saved_property", i.someField);
return sec;
}
完整样例
这里是LayoutEditorElement
的完整样例。
package de.keksuccino.fancymenu.api.item.example;
import de.keksuccino.fancymenu.api.item.LayoutEditorElement;
import de.keksuccino.fancymenu.menu.fancy.helper.DynamicValueInputPopup;
import de.keksuccino.fancymenu.menu.fancy.helper.layoutcreator.LayoutEditorScreen;
import de.keksuccino.fancymenu.menu.fancy.helper.ui.popup.FMTextInputPopup;
import de.keksuccino.konkrete.gui.content.AdvancedButton;
import de.keksuccino.konkrete.gui.screens.popup.PopupHandler;
import de.keksuccino.konkrete.rendering.RenderUtils;
import java.awt.*;
public class ExampleLayoutEditorElement extends LayoutEditorElement {
public ExampleLayoutEditorElement(ExampleCustomizationItemContainer parentContainer, ExampleCustomizationItem customizationItemInstance, LayoutEditorScreen handler) {
super(parentContainer, customizationItemInstance, true, handler, true);
}
@Override
public void init() {
//The superclass adds basic stuff to the right-click context menu, like visibility requirement controls, delete controls, orientation, etc.
//这里的超类为右键菜单添加了些基础功能,比如可视化需求控件,删除控件,方向等。
super.init();
//The 'object' field holds the CustomizationItem instance of this element.
//"object"字段包含这个元素的CustomizationItem实例。
//Cast it to your own item class, to get and set your own fields.
//把他转换成你自己的项类,以便于获取/设置你自己的字段。
ExampleCustomizationItem i = ((ExampleCustomizationItem)this.object);
//This button will be part of the right-click context menu of the element and is uses to change the background color value of the item.
//这个按钮将成为元素右键菜单的一部分,用于更改项的背景颜色值。
AdvancedButton backgroundColorButton = new AdvancedButton(0, 0, 0, 0, "Background Color", (press) -> {
//This is the basic input popup for text content, used in many parts of FancyMenu.
//这是文本内容的基本输入弹出框,在FancyMenu的很多部分都有使用。
FMTextInputPopup pop = new FMTextInputPopup(new Color(0, 0, 0, 0), "Background Color HEX", null, 240, (callback) -> {
//The callback of popups will be null, when pressing ESC in it to force-close it.
//当按下ESC强制关闭窗口时,窗口回调值为空。
if (callback != null) {
if (!callback.equals(i.backgroundColorString)) {
Color c = RenderUtils.getColorFromHexString(callback);
if (c != null) {
//Create a snapshot before every change, so you can undo the change in the editor (using CTRL + Z)
//在每次更改前生成一个快照,顺便你也可以回退在编辑器中的更改(Ctrl+Z)
this.handler.history.saveSnapshot(this.handler.history.createSnapshot());
//Now set the new values to the item instance
//现在捏,给项实例设置新值。
i.backgroundColorString = callback;
i.backgroundColor = c;
}
}
}
});
//Set the current value as default text of the text input popup
//设置现有的值为文本输入框的默认文本。
if (i.backgroundColorString != null) {
pop.setText(i.backgroundColorString);
}
//Open the popup
//打开弹出框。
PopupHandler.displayPopup(pop);
});
backgroundColorButton.setDescription("This is just an example button tooltip.");
//Add the button to the right-click context menu content
//添加按钮到右键菜单内容中。
this.rightclickMenu.addContent(backgroundColorButton);
//This is the button to change the display text of the item. Will also be part of the right-click context menu.
//这个是更改项显示的文本的按钮,也会成为右键菜单的一部分。
AdvancedButton displayTextButton = new AdvancedButton(0, 0, 0, 0, "Display Text", (press) -> {
//This is also a text input popup, but with placeholder text value support (the little icon at the right side of the input field)
//这也是文本弹出框,但是支持占位符文本值。
DynamicValueInputPopup pop = new DynamicValueInputPopup(new Color(0, 0, 0, 0), "Set Display Text", null, 240, (callback) -> {
if (callback != null) {
if (!callback.equals(i.displayText)) {
//Again, save a snapshot before changing something!
//再来一次,在改变之前保存快照。
this.handler.history.saveSnapshot(this.handler.history.createSnapshot());
//Setting the new display text value
//设置新的显示文本值。
i.displayText = callback;
}
}
});
if (i.displayText != null) {
pop.setText(i.displayText);
}
PopupHandler.displayPopup(pop);
});
this.rightclickMenu.addContent(displayTextButton);
}
@Override
public SimplePropertiesSection serializeItem() {
ExampleCustomizationItem i = ((ExampleCustomizationItem)this.object);
SimplePropertiesSection sec = new SimplePropertiesSection();
//Add your custom item values here, so they get saved and can later be de-serialized again.
sec.addEntry("background_color", i.backgroundColorString);
sec.addEntry("display_text", i.displayText);
return sec;
}
}
创建Container
我们需要的最后的类是CustomizationItemContainer
的子类。
这个容器是CustomizationItems
和LayoutEditorElements
的基础实例Builder。
给你的项创建CustomizationItemContainer
子类并起合适的名字。
接下来,我会将我的示例容器类命名为ExampleCustomizationItemContainer
。
Item Identifier
项容器的构造方法需要唯一的标识符。(像是sfz)
至于怎么让你的标识符唯一,我也不赘述了。
你应该在子类构造方法里直接设置标识符。
public ExampleCustomizationItemContainer() {
super("example_item_identifier");
}
Instance Builders
项容器用于构建你的CustomizationItem
子类和LayoutEditorElement
子类的实例。
你需要设置Builder的方法,以便容器可以构建子类的实例。
//This will construct a default instance of your CustomizationItem without any customizations made to it.
//这里将会构造一个你的CustomizationItem默认实例,而且不对其进行任何自定义。
@Override
public CustomizationItem constructDefaultItemInstance() {
//Just use an empty properties section here.
//这里只是用了空属性section。
//Make sure that your CustomizationItem accepts empty sections without throwing errors!
//请确保你的CustomizationItem能接受空section还不报错。
ExampleCustomizationItem i = new ExampleCustomizationItem(this, new PropertiesSection("dummy"));
//The default size of 10x10 would be a bit too small for the item, so I set a new default size of 100x100 to the default instance.
//默认10*10的大小可能对于项来说有点过小了,所以我把10*10拉到了100*100。
//This means that now every new item of this type will have a size of 100x100 by default.
//就是说,咱现在所有这类型的新项默认大小都是100*100了。
i.width = 100;
i.height = 100;
return i;
}
//This will construct a customized instance of your CustomizationItem, using the given PropertiesSection (serialized item) to set all customizations.
//这将构造您的CustomizationItem 的自定义实例,使用给定的PropertiesSection(序列化项)来设置所有自定义项。
@Override
public CustomizationItem constructCustomizedItemInstance(PropertiesSection serializedItem) {
return new ExampleCustomizationItem(this, serializedItem);
}
//This will construct a new instance of your LayoutEditorElement, used in the layout editor.
//这里将会为你的LayoutEditorElement构建用在布局编辑器的实例。
@Override
public LayoutEditorElement constructEditorElementInstance(CustomizationItem item, LayoutEditorScreen handler) {
return new ExampleLayoutEditorElement(this, (ExampleCustomizationItem) item, handler);
}
Display Name
你应该为你的项设置显示名称。
这个显示名会用在布局编辑器中。
这个方法允许你填入本地化键作为显示名称。
@Override
public String getDisplayName() {
return "Example Item";
}
描述
将鼠标悬停在按钮上以在布局编辑器中添加此类型的新项时,会显示项的描述。
@Override
public String[] getDescription() {
return new String[] {
"This is a description",
"with 2 lines of text."
};
}
完整示例
这里是CustomizationItemContainer
的一个完整样例。
package de.keksuccino.fancymenu.api.item.example;
import de.keksuccino.fancymenu.api.item.CustomizationItem;
import de.keksuccino.fancymenu.api.item.CustomizationItemContainer;
import de.keksuccino.fancymenu.api.item.LayoutEditorElement;
import de.keksuccino.fancymenu.menu.fancy.helper.layoutcreator.LayoutEditorScreen;
import de.keksuccino.konkrete.properties.PropertiesSection;
//This needs to be registered to the CustomizationItemRegistry at mod init
//在模组初始化这里需要被注册到CustomizationItemRegistry。
public class ExampleCustomizationItemContainer extends CustomizationItemContainer {
public ExampleCustomizationItemContainer() {
super("example_item_identifier");
}
@Override
public CustomizationItem constructDefaultItemInstance() {
ExampleCustomizationItem i = new ExampleCustomizationItem(this, new PropertiesSection("dummy"));
//The default size of 10x10 would be a bit too small for the item, so I set a new default size of 100x100 to the default instance.
//默认10*10的大小可能对于项来说有点过小了,所以我把10*10拉到了100*100。
//This means that now every new item of this type will have a size of 100x100 by default.
//就是说,咱现在所有这类型的新项默认大小都是100*100了。
i.width = 100;
i.height = 100;
return i;
}
@Override
public CustomizationItem constructCustomizedItemInstance(PropertiesSection serializedItem) {
return new ExampleCustomizationItem(this, serializedItem);
}
@Override
public LayoutEditorElement constructEditorElementInstance(CustomizationItem item, LayoutEditorScreen handler) {
return new ExampleLayoutEditorElement(this, (ExampleCustomizationItem) item, handler);
}
@Override
public String getDisplayName() {
return "Example Item";
}
@Override
public String[] getDescription() {
return new String[] {
"This is a description",
"with 2 lines of text."
};
}
}
注册Container
你就快完成了,嗯...最后一步,人类的一大步。
FancyMenu需要认识你的自定义项,所以你需要让你的拓展模组初始化时注册你的CustomizationItemContainer
到CustomizationItemRegistery
。
package de.keksuccino.fancymenu;
import net.minecraftforge.fml.common.Mod;
import de.keksuccino.fancymenu.api.item.CustomizationItemRegistry;
@Mod("modid")
public class ExampleModMainClass {
public ExampleModMainClass() {
try {
//Register your CustomizationItemContainer to the CustomizationItemRegistry at mod init.
//初始化时注册你的CustomizationItemContainer到CustomizationItemRegistery。
CustomizationItemRegistry.registerItem(new ExampleCustomizationItemContainer());
} catch (Exception e) {
e.printStackTrace();
}
}
}
现在你可以使用你自己的布局元素了!