🇺🇦 Українська (Ukrainian - Ukraine)
🇺🇦 Українська (Ukrainian - Ukraine)
Зовнішній вигляд
🇺🇦 Українська (Ukrainian - Ukraine)
🇺🇦 Українська (Ukrainian - Ukraine)
Зовнішній вигляд
Ця сторінка написана для версії:
1.21.4
Ця сторінка написана для версії:
1.21.4
Оскільки ваші предмети стають складнішими, вам може знадобитися зберігати спеціальні дані, пов’язані з кожним предметом. Гра дозволяє зберігати постійні дані в ItemStack
, а з версії 1.20.5 ми це робимо за допомогою компонентів даних.
Компоненти даних замінюють дані NBT із попередніх версій структурованими типами даних, які можна застосувати до ItemStack
для зберігання постійних даних про цей стек. Компоненти даних мають простір назви, тобто ми можемо реалізувати власні компоненти даних для зберігання спеціальних даних про ItemStack
і доступу до них пізніше. Повний список компонентів даних можна знайти на цій вікі-сторінці 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()
);
Тут варто звернути увагу на кілька речей. У першому та четвертому рядках ви можете побачити ?
. Це буде замінено типом значення вашого компонента. Ми незабаром заповнимо це.
По-друге, ви повинні надати Identifier
, що містить призначений ідентифікатор вашого компонента. Це простір назв з ID вашого мода.
Нарешті, у нас є ComponentType.Builder
, який створює фактичний екземпляр ComponentType
, який реєструється. Тут міститься ще одна важлива деталь, яку нам потрібно буде обговорити: Кодек
вашого компонента. Наразі це null
, але незабаром ми його також заповнимо.
Звичайні компоненти даних (наприклад, 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
, для української uk_ua.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]
Коли ви наведете курсор на цей предмет у своєму інвентарі, ви побачите кількість, що показується в спливаючій підказці!
Коли ви наведете курсор на цей предмет у своєму інвентарі, ви побачите кількість, що показується в спливаючій підказці!
Однак, якщо ви дасте собі новий лічильник без спеціального компонента, гра аварійно завершить роботу, коли ви наведете курсор на предмет у своєму інвентарі. Ви повинні побачити таку помилку у звіті про збій:
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)
Як і очікувалося, оскільки ItemStack
наразі не містить екземпляра нашого спеціального компонента, виклик stack.get()
із нашим типом компонента поверне значення null
.
Як і очікувалося, оскільки ItemStack
наразі не містить екземпляра нашого спеціального компонента, виклик stack.get()
із нашим типом компонента поверне значення null
.
Коли ви реєструєте свій предмет і передаєте об’єкт Item.Settings
своєму конструктору предмета, ви також можете надати список компонентів за замовчуванням, які застосовуються до всіх нових предметів. Якщо ми повернемося до нашого класу ModItems
, де ми реєструємо CounterItem
, ми зможемо додати значення за замовчуванням для нашого спеціального компонента. Додайте це, щоб нові предмети показували кількість «0».
public static final RegistryKey<Item> COUNTER_KEY = RegistryKey.of(RegistryKeys.ITEM, Identifier.of(FabricDocsReference.MOD_ID, "counter"));
public static final Item COUNTER = register(new CounterItem(
new Item.Settings()
.registryKey(COUNTER_KEY)
// Initialize the click count component with a default value of 0
.component(ModComponents.CLICK_COUNT_COMPONENT, 0)
), COUNTER_KEY);
Коли створюється новий предмет, до нього автоматично застосовуватиметься наш спеціальний компонент із заданим значенням.
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 разів» і більше не завершує роботу гри.
Запустіть гру ще раз і наведіть вказівник мишки на предмет без компонента, ви повинні побачити, що він показує «Використано 0 разів» і більше не завершує роботу гри.
Спробуйте створити собі лічильник, видаливши наш спеціальний компонент. Для цього можна використати цю команду:
/give @p fabric-docs-reference:counter[!fabric-docs-reference:click_count]
При наведенні курсора на цей предмет підказка повинна бути відсутня.
При наведенні курсора на цей предмет підказка повинна бути відсутня.
Тепер спробуймо оновити значення нашого компонента. Ми будемо збільшувати кількість натискань кожного разу, коли використовуємо наш лічильник. Щоб змінити значення компонента в ItemStack
, ми використовуємо метод set()
таким чином:
stack.set(ModComponents.CLICK_COUNT_COMPONENT, newValue);
Це бере наш тип компонента та значення, яке ми хочемо встановити. У цьому випадку це буде наша нова кількість натискань. Цей метод також повертає старе значення компонента (якщо воно є), що може бути корисним у деяких ситуаціях. Наприклад:
int oldValue = stack.set(ModComponents.CLICK_COUNT_COMPONENT, newValue);
Налаштуймо новий метод use()
, щоб зчитувати стару кількість натискань, збільшити її на один, а потім встановити оновлену кількість натискань.
public ActionResult use(World world, PlayerEntity user, Hand hand) {
ItemStack stack = user.getStackInHand(hand);
// Don't do anything on the client
if (world.isClient()) {
return ActionResult.SUCCESS;
}
// 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 ActionResult.SUCCESS;
}
Тепер спробуйте запустити гру та натиснути ПКМ з предметом лічильника в руці. Якщо ви відкриєте свій інвентар і подивіться на предмет знову, ви побачите, що число використання зросло на кількість разів, які ви натискали на нього.
Ви також можете видалити компонент зі свого 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() {
}
Зверніть увагу, що після назви класу є набір дужок. Тут ми визначаємо список властивостей, які ми хочемо мати у нашого компонента. Додаймо float і логічне значення, які називаються temperature
і burnt
відповідно.
public record MyCustomComponent(float temperature, boolean burnt) {
}
Оскільки ми визначаємо спеціальну структуру даних, для нашого випадку використання не буде попередньо існуючого Codec
, як у випадку з базовим компонентом. Це означає, що нам доведеться створити власний кодек. Визначмо один у нашому класі записів за допомогою 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
. Використовуйте відповідально!