🇷🇺 Русский (Russian)
🇷🇺 Русский (Russian)
Внешний вид
🇷🇺 Русский (Russian)
🇷🇺 Русский (Russian)
Внешний вид
This page is written for version:
1.21
This page is written for version:
1.21
По мере того как ваши элементы становятся более сложными, вам может потребоваться хранить специальные данные, связанные с каждым элементом. Игра позволяет хранить постоянные данные в ItemStack
, и начиная с версии 1.20.5 мы делаем это с помощью Компонентов данных.
Компоненты данных заменяют данные NBT из предыдущих версий структурированными типами данных, которые можно применять к ItemStack
для хранения постоянных данных об этом стеке. Компоненты данных имеют пространство имен, что означает, что мы можем реализовать собственные компоненты данных для хранения пользовательских данных о ItemStack
и доступа к ним позже. Полный список компонентов данных vanilla можно найти на этой странице вики Minecraft.
Наряду с регистрацией пользовательских компонентов на этой странице рассматривается общее использование API компонентов, которое также применимо к ванильным компонентам. Вы можете просмотреть и получить доступ к определениям всех ванильных компонентов в классе DataComponentTypes
.
Как и все остальное в вашем моде, вам необходимо зарегистрировать свой пользовательский компонент с помощью ComponentType
. Этот тип компонента принимает универсальный аргумент, содержащий тип значения вашего компонента. Мы более подробно рассмотрим это далее, когда будем рассматривать базовые и расширенные компоненты.
Выберите подходящий класс для размещения этого кода. В этом примере мы создадим новый пакет с именем component
и класс, содержащий все типы наших компонентов, с именем ModComponents
. Обязательно вызовите ModComponents.initialize()
в инициализаторе мода.
public class ModComponents {
protected static void initialize() {
FabricDocsReference.LOGGER.info("Registering {} components", FabricDocsReference.MOD_ID);
// Technically this method can stay empty, but some developers like to notify
// the console, that certain parts of the mod have been successfully initialized
}
}
Это базовый шаблон для регистрации типа компонента:
public static final ComponentType<?> MY_COMPONENT_TYPE = Registry.register(
Registries.DATA_COMPONENT_TYPE,
Identifier.of(FabricDocsReference.MOD_ID, "my_component"),
ComponentType.<?>builder().codec(null).build()
);
Здесь есть несколько вещей, на которые стоит обратить внимание. В первой и четвертой строках вы можете увидеть ?
. Он будет заменен типом значения вашего компонента. Мы скоро это заполним.
Во-вторых, вы должны предоставить Идентификатор
, содержащий предполагаемый идентификатор вашего компонента. Это пространство имен с идентификатором вашего мода.
Наконец, у нас есть ComponentType.Builder
, который создает фактический экземпляр ComponentType
и регистрируется. Здесь содержится еще одна важная деталь, которую нам нужно будет обсудить: Codec
вашего компонента. В настоящее время это поле пустое, но мы скоро его заполним.
Базовые компоненты данных (например, minecraft:damage
) состоят из одного значения данных, например int
, float
, boolean
или String
.
В качестве примера давайте создадим значение Integer
, которое будет отслеживать, сколько раз игрок щелкнул правой кнопкой мыши, удерживая наш предмет. Давайте обновим регистрацию нашего компонента следующим образом:
public static final ComponentType<Integer> CLICK_COUNT_COMPONENT = Registry.register(
Registries.DATA_COMPONENT_TYPE,
Identifier.of(FabricDocsReference.MOD_ID, "click_count"),
ComponentType.<Integer>builder().codec(Codec.INT).build()
);
Вы можете видеть, что теперь мы передаем <Integer>
в качестве нашего универсального типа, указывая, что этот компонент будет сохранен как одно значение int
. В качестве нашего кодека мы используем предоставленный кодек Codec.INT
. Для таких простых компонентов, как этот, можно обойтись использованием базовых кодеков, но для более сложных сценариев может потребоваться специальный кодек (об этом мы кратко поговорим позже).
Если вы запустите игру, вы сможете ввести такую команду:
При выполнении команды вы должны получить элемент, содержащий компонент. Однако в настоящее время мы не используем наш компонент для каких-либо полезных целей. Давайте начнем с прочтения значения компонента таким образом, чтобы мы могли его увидеть.
Давайте добавим новый элемент, который будет увеличивать счетчик каждый раз, когда по нему щелкают правой кнопкой мыши. Вам следует прочитать страницу Взаимодействие с пользовательскими элементами, где описаны методы, которые мы будем использовать в этом руководстве.
public class CounterItem extends Item {
public CounterItem(Settings settings) {
super(settings);
}
}
Не забудьте, как обычно, зарегистрировать элемент в классе ModItems
.
public static final Item COUNTER = register(new CounterItem(
new Item.Settings()
), "counter");
Мы добавим код подсказки, чтобы отображать текущее значение количества кликов при наведении курсора на наш предмет в инвентаре. Мы можем использовать метод get()
в нашем ItemStack
, чтобы получить значение нашего компонента следующим образом:
int clickCount = stack.get(ModComponents.CLICK_COUNT_COMPONENT);
Это вернет текущее значение компонента как тип, который мы определили при регистрации нашего компонента. Затем мы можем использовать это значение для добавления записи всплывающей подсказки. Добавьте эту строку в метод appendTooltip
в классе CounterItem
:
public void appendTooltip(ItemStack stack, TooltipContext context, List<Text> tooltip, TooltipType type) {
int count = stack.get(ModComponents.CLICK_COUNT_COMPONENT);
tooltip.add(Text.translatable("item.fabric-docs-reference.counter.info", count).formatted(Formatting.GOLD));
}
Не забудьте обновить свой языковой файл (/assets/<mod id>/lang/en_us.json
) и добавить в него следующие две строки:
{
"item.fabric-docs-reference.counter": "Counter",
"item.fabric-docs-reference.counter.info": "Used %1$s times",
}
Запустите игру и выполните эту команду, чтобы получить новый предмет счетчик со значением 5.
/give @p fabric-docs-reference:counter[fabric-docs-reference:click_count=5]
При наведении курсора на этот предмет в инвентаре вы увидите количество, отображаемое во всплывающей подсказке!
Однако если вы дадите себе новый предмет Counter без пользовательского компонента, игра вылетит при наведении курсора на предмет в инвентаре. В отчете о сбое вы должны увидеть такую ошибку:
java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "net.minecraft.item.ItemStack.get(net.minecraft.component.ComponentType)" is null
at com.example.docs.item.custom.CounterItem.appendTooltip(LightningStick.java:45)
at net.minecraft.item.ItemStack.getTooltip(ItemStack.java:767)
As expected, since the ItemStack
doesn't currently contain an instance of our custom component, calling stack.get()
with our component type will return null
.
Для решения этой проблемы мы можем использовать три решения.
Когда вы регистрируете свой элемент и передаете объект Item.Settings
в конструктор элемента, вы также можете предоставить список компонентов по умолчанию, которые применяются ко всем новым элементам. Если вернуться к нашему классу ModItems
, где мы регистрируем CounterItem
, мы можем добавить значение по умолчанию для нашего пользовательского компонента. Добавьте это, чтобы для новых элементов отображалось количество «0».
public static final Item COUNTER = register(new CounterItem(
// Initialize the click count component with a default value of 0
new Item.Settings().component(ModComponents.CLICK_COUNT_COMPONENT, 0)
), "counter");
При создании нового элемента он автоматически применит наш пользовательский компонент с заданным значением.
WARNING
Используя команды, можно удалить компонент по умолчанию из ItemStack
. Вам следует обратиться к следующим двум разделам, чтобы правильно обработать ситуацию, когда компонент отсутствует в вашем изделии.
Кроме того, при чтении значения компонента мы можем использовать метод getOrDefault()
нашего объекта ItemStack
, чтобы вернуть указанное значение по умолчанию, если компонент отсутствует в стеке. Это защитит от любых ошибок, возникающих из-за отсутствия какого-либо компонента. Мы можем настроить код нашей подсказки следующим образом:
int clickCount = stack.getOrDefault(ModComponents.CLICK_COUNT_COMPONENT, 0);
Как видите, этот метод принимает два аргумента: тип нашего компонента, как и раньше, и значение по умолчанию, которое будет возвращаться, если компонент отсутствует.
Вы также можете проверить наличие определенного компонента в ItemStack
, используя метод contains()
. Он принимает тип компонента в качестве аргумента и возвращает true
или false
в зависимости от того, содержит ли стек этот компонент.
boolean exists = stack.contains(ModComponents.CLICK_COUNT_COMPONENT);
Мы выберем третий вариант. Поэтому наряду с добавлением значения компонента по умолчанию мы также проверим, присутствует ли компонент в стеке, и покажем подсказку только в том случае, если это так.
public void appendTooltip(ItemStack stack, TooltipContext context, List<Text> tooltip, TooltipType type) {
if (stack.contains(ModComponents.CLICK_COUNT_COMPONENT)) {
int count = stack.get(ModComponents.CLICK_COUNT_COMPONENT);
tooltip.add(Text.translatable("item.fabric-docs-reference.counter.info", count).formatted(Formatting.GOLD));
}
}
Запустите игру снова и наведите курсор на предмет без компонента. Вы должны увидеть надпись «Использовано 0 раз», и игра больше не будет вылетать.
Попробуйте создать свой счетчик, удалив наш пользовательский компонент. Для этого можно использовать следующую команду:
/give @p fabric-docs-reference:counter[!fabric-docs-reference:click_count]
При наведении курсора на этот элемент подсказка должна отсутствовать.
Теперь попробуем обновить значение нашего компонента. Мы собираемся увеличивать количество кликов каждый раз, когда используем наш элемент Counter. Чтобы изменить значение компонента в ItemStack
, мы используем метод set()
следующим образом:
stack.set(ModComponents.CLICK_COUNT_COMPONENT, newValue);
Он принимает тип нашего компонента и значение, которое мы хотим ему присвоить. В данном случае это будет наше новое количество кликов. Этот метод также возвращает старое значение компонента (если оно присутствует), что может быть полезно в некоторых ситуациях. Например:
int oldValue = stack.set(ModComponents.CLICK_COUNT_COMPONENT, newValue);
Давайте настроим новый метод use()
для считывания старого количества кликов, увеличения его на единицу, а затем установки обновленного количества кликов.
public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
ItemStack stack = user.getStackInHand(hand);
// Don't do anything on the client
if (world.isClient()) {
return TypedActionResult.success(stack);
}
// Read the current count and increase it by one
int count = stack.getOrDefault(ModComponents.CLICK_COUNT_COMPONENT, 0);
stack.set(ModComponents.CLICK_COUNT_COMPONENT, ++count);
// Return the original stack
return TypedActionResult.success(stack);
}
Теперь попробуйте запустить игру и щелкнуть правой кнопкой мыши, держа в руке предмет Counter. Если вы откроете свой инвентарь и снова посмотрите на предмет, вы увидите, что показатель использования увеличился пропорционально количеству кликов по нему.
Вы также можете удалить компонент из ItemStack
, если он больше не нужен. Это делается с помощью метода remove()
, который принимает тип вашего компонента.
stack.remove(ModComponents.CLICK_COUNT_COMPONENT);
Этот метод также возвращает значение компонента до его удаления, поэтому его можно использовать следующим образом:
int oldCount = stack.remove(ModComponents.CLICK_COUNT_COMPONENT);
Возможно, вам придется хранить несколько атрибутов в одном компоненте. В качестве простого примера компонент minecraft:food
хранит несколько значений, связанных с едой, таких как nutrition
, saturation
, eat_seconds
и другие. В этом руководстве мы будем называть их «композитными» компонентами.
Для составных компонентов необходимо создать класс record
для хранения данных. Это тип, который мы зарегистрируем в нашем типе компонента, и то, что мы будем читать и записывать при взаимодействии с ItemStack
. Начнем с создания нового класса записи в пакете component
, который мы создали ранее.
public record MyCustomComponent() {
}
Обратите внимание, что после имени класса стоят скобки. Здесь мы определяем список свойств, которые должен иметь наш компонент. Давайте добавим число с плавающей точкой и логическое значение с именами температура
и сожженный
соответственно.
public record MyCustomComponent(float temperature, boolean burnt) {
}
Поскольку мы определяем пользовательскую структуру данных, для нашего варианта использования не будет готового Кодека
, как в случае с базовым компонентом. Это значит, что нам придется создать собственный кодек. Давайте определим его в нашем классе записи, используя RecordCodecBuilder
, на который мы сможем ссылаться после регистрации компонента. Более подробную информацию об использовании RecordCodecBuilder
можно найти в этом разделе страницы Кодеки.
public static final Codec<MyCustomComponent> CODEC = RecordCodecBuilder.create(builder -> {
return builder.group(
Codec.FLOAT.fieldOf("temperature").forGetter(MyCustomComponent::temperature),
Codec.BOOL.optionalFieldOf("burnt", false).forGetter(MyCustomComponent::burnt)
).apply(builder, MyCustomComponent::new);
});
Вы можете видеть, что мы определяем список пользовательских полей на основе примитивных типов Codec
. Однако мы также сообщаем ей, как называются наши поля, используя fieldOf()
, а затем используем forGetter()
, чтобы сообщить игре, какой атрибут нашей записи следует заполнить.
Вы также можете определить необязательные поля, используя optionalFieldOf()
и передав значение по умолчанию в качестве второго аргумента. Любые поля, не отмеченные как необязательные, будут обязательными при настройке компонента с помощью /give
, поэтому обязательно отметьте все необязательные аргументы как таковые при создании кодека.
Наконец, мы вызываем apply()
и передаем конструктор нашей записи. Более подробную информацию о создании кодеков и более сложных вариантах использования можно найти на странице Кодеки.
Регистрация составного компонента аналогична предыдущей. Мы просто передаем наш класс записи как универсальный тип, а наш пользовательский Codec
— в метод codec()
.
public static final ComponentType<MyCustomComponent> MY_CUSTOM_COMPONENT = Registry.register(
Registries.DATA_COMPONENT_TYPE,
Identifier.of(FabricDocsReference.MOD_ID, "custom"),
ComponentType.<MyCustomComponent>builder().codec(MyCustomComponent.CODEC).build()
);
Теперь начинайте игру. Попробуйте применить компонент с помощью команды /give
. Значения составных компонентов передаются как объект, заключенный в {}
. Если вы поставите пустые фигурные скобки, вы увидите сообщение об ошибке, сообщающее, что требуемый ключ temperature
отсутствует.
Добавьте значение температуры к объекту, используя синтаксис temperature:8.2
. При желании можно также передать значение для burnt
, используя тот же синтаксис, но либо true
, либо false
. Теперь вы должны увидеть, что команда действительна и может предоставить вам элемент, содержащий компонент.
Использование компонента в коде такое же, как и раньше. Использование stack.get()
вернет экземпляр вашего класса record
, который затем можно использовать для чтения значений. Поскольку записи доступны только для чтения, вам потребуется создать новый экземпляр записи, чтобы обновить значения.
// read values of component
MyCustomComponent comp = stack.get(ModComponents.MY_CUSTOM_COMPONENT);
float temp = comp.temperature();
boolean burnt = comp.burnt();
// set new component values
stack.set(ModComponents.MY_CUSTOM_COMPONENT, new MyCustomComponent(8.4f, true));
// check for component
if (stack.contains(ModComponents.MY_CUSTOM_COMPONENT)) {
// do something
}
// remove component
stack.remove(ModComponents.MY_CUSTOM_COMPONENT);
Вы также можете задать значение по умолчанию для составного компонента, передав объект компонента в Item.Settings
. Например:
public static final Item COUNTER = register(new CounterItem(
new Item.Settings().component(ModComponents.MY_CUSTOM_COMPONENT, new MyCustomComponent(0.0f, false))
), "counter");
Теперь вы можете хранить пользовательские данные в ItemStack
. Используйте ответственно!