Динамічна реєстрація 26.2
Дізнайтеся, про динамічні реєстри: що це таке, коли вони стають у пригоді та як створити власний за допомогою Fabric API.
Реєстр — це централізована «телефонна книга», що зіставляє унікальні ID, як-от minecraft:items, із конкретними об'єктами.
Існує два типи реєстрів: статичні реєстри, як-от реєстри блоків і предметів, які фіксуються під час запуску, тоді як динамічні, тобто власні, заповнюються під час виконання програми даними з JSON-файлів у пакетах даних.
Вони корисні з багатьох причин:
- Вони відділяють логіку від умісту.
- Інші творці модів можуть додавати новий уміст за допомогою пакетів даних, замість того щоб вносити зміни безпосередньо у ваш код.
- Гравці можуть змінювати стандартні значення, як-от вартість у мані чи ціну покращення, замінюючи записи даних у пакеті даних.
- Динамічні реєстри належать конкретному світу. Вони завантажуються під час відкриття світу й очищується, коли цей світ закривається.
- Динамічні реєстри розв'язують проблему «жорстко закодованого вмісту». Замість того щоб жорстко прописувати кожне вміння, завдання чи покращення безпосередньо в коді Java за допомогою переліків або статичних списків, ви визначаєте в коді шаблон, а фактичний вміст завантажуєте з даних.
Створімо динамічний реєстр для системи магічних умінь.
Налаштування класу
Спершу створіть клас, що представляє запис у реєстрі. Це простий контейнер даних для значень, пов’язаних із кожним магічним умінням, як-от назва, вартість у мані тощо. Для кодування та декодування запису потрібен Codec.
java
public record MagicSkillsRegistryEntry(String name, int manaCost, Optional<CacheableFunction> onUseMcFunction) {
public static final Codec<MagicSkillsRegistryEntry> CODEC = RecordCodecBuilder.create(instance ->
instance.group(
Codec.STRING.fieldOf("name").forGetter(MagicSkillsRegistryEntry::name),
Codec.INT.fieldOf("mana_cost").forGetter(MagicSkillsRegistryEntry::manaCost),
CacheableFunction.CODEC.optionalFieldOf("on_use_mc_function").forGetter(MagicSkillsRegistryEntry::onUseMcFunction)
).apply(instance, MagicSkillsRegistryEntry::new)
);
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
name— назва вміння.manaCost— вартість у мані для вміння.onUseMcFunction— функція, яку сервер може виконувати при використанні вміння. Наявність цього в реєстрі дозволить іншим пакетам даних налаштовувати логіку будь-якого вміння або додавати нові вміння з власними функціями.
Реєстрація реєстру
Кожен реєстр реєструється за допомогою ключа, що його однозначно ідентифікує, тож створимо цей ключ і клас для його зберігання. Ми назвемо його ExampleModRegistries:
TIP
Рекомендується оголошувати ключі реєстру в спільному класі, оскільки це полегшить керування кількома реєстрами.
java
public class ExampleModRegistries {
public static final ResourceKey<Registry<MagicSkillsRegistryEntry>> MAGIC_SKILLS_REGISTRY_KEY =
ResourceKey.createRegistryKey(ExampleMod.id("magic_skills_registry"));
public static void initialize() {
// Register Code Here
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Виклик ExampleModRegistries.initialize() з нашого ініціалізатора мода.
java
public class ExampleModDynamicRegistries implements ModInitializer {
@Override
public void onInitialize() {
ExampleModRegistries.initialize();
}
}1
2
3
4
5
6
2
3
4
5
6
Потім зареєструйте його за допомогою DynamicRegistries з Fabric API, що пропонує дві різні стратегії: DynamicRegistries.register() або DynamicRegistries.registerSynced().
Використання register()
DynamicRegistries.register() створює несинхронізований реєстр. Він є лише на сервері, але немає на клієнті. Використовуйте це, коли клієнту ніколи не потрібно читати реєстр.
У нашому прикладі це не має значення, але ось як це зробити:
java
DynamicRegistries.register(MAGIC_SKILLS_REGISTRY_KEY, MagicSkillsRegistryEntry.CODEC);1
Використання registerSynced()
INFO
Ключі, що використовуються в наведених нижче методах, створюються так само, як ми створили MAGIC_SKILLS_REGISTRY_KEY, але з іншою назвою.
DynamicRegistries.registerSynced() створює синхронізований реєстр. Коли клієнт приєднується до світу, сервер автоматично синхронізує дані цього реєстру з клієнтом. Використовуйте це, коли клієнту потрібні дані для рендера, інтерфейсу, підказок або іншої логіки на стороні клієнта.
java
DynamicRegistries.registerSynced(MAGIC_SKILLS_SYNCED_REGISTRY_KEY, MagicSkillsRegistryEntry.CODEC);1
DynamicRegistries.registerSynced() має перевантаження, яке приймає другий кодек для декодування на стороні клієнта. Це корисно, якщо клієнту не потрібні всі поля з повного запису на сервері.
У нашому випадку на клієнтській стороні потрібні лише поля name і manaCost, тому створимо Codec, який не містить onUseMcFunction, і передамо його до registerSynced:
java
public record MagicSkillsRegistryEntry(String name, int manaCost, Optional<CacheableFunction> onUseMcFunction) {
// Other Variables and Methods
public static final Codec<MagicSkillsRegistryEntry> CLIENT_CODEC = RecordCodecBuilder.create(instance ->
instance.group(
Codec.STRING.fieldOf("name").forGetter(MagicSkillsRegistryEntry::name),
Codec.INT.fieldOf("mana_cost").forGetter(MagicSkillsRegistryEntry::manaCost)
).apply(instance, (name, manaCost) -> new MagicSkillsRegistryEntry(name, manaCost, Optional.empty()))
);
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
java
DynamicRegistries.registerSynced(MAGIC_SKILLS_DOUBLE_CODEC_REGISTRY_KEY, MagicSkillsRegistryEntry.CODEC, MagicSkillsRegistryEntry.CLIENT_CODEC);1
SyncOption
Обидві перевантажені версії методу DynamicRegistries.registerSynced() приймають аргументи SyncOption у кінці списку параметрів для налаштування поведінки синхронізації. Єдиний доступний варіант для використання:
SKIP_WHEN_EMPTY: Синхронізує реєстр лише тоді, коли він містить записи. Це може допомогти із сумісністю для клієнтів, яким може не знадобитися реєстр.
Приклад:
java
DynamicRegistries.registerSynced(MAGIC_SKILLS_WITH_OPTION_REGISTRY_KEY, MagicSkillsRegistryEntry.CODEC, DynamicRegistries.SyncOption.SKIP_WHEN_EMPTY);1
Заповнення реєстру
JSON-файли використовуються для створення записів у реєстрі. Структура JSON повинна відповідати MagicSkillsRegistryEntry. У цьому прикладі наш клас запису має три поля, тому JSON-файл для запису healing_skill може виглядати так:
json
{
"mana_cost": 4785,
"name": "Healing Skill",
"on_use_mc_function": "example-mod:healing_skill_function"
}1
2
3
4
5
2
3
4
5
Початкові файли JSON зберігаються в теці src/main/resources/data/example-mod/example-mod/magic_skills_registry/.
INFO
Повторення example-mod/example-mod не є помилкою.
Перший example-mod — це простір імен запису, що додається. Другий example-mod походить від самого ID реєстру. Використання ID вашого мода для обох випадків є стандартною практикою; це дозволяє іншим модам або пакетам даних додавати записи до вашого реєстру, використовуючи власні простори імен.
Наприклад, another-mod може захотіти додати елементи до нашого magic_skills_registry; для цього він використовуватиме файли, розміщені за шляхом src/main/resources/data/another-mod/example-mod/magic_skills_registry/.
ID запису
ID запису — це унікальний ключ для кожного запису; він може бути корисним для доступу до конкретного запису в реєстрі. Він складається з імені файлу та ключа реєстру. Наприклад, оскільки наш початковий JSON-файл має назву healing_skill.json, ID запису:
java
ResourceKey<MagicSkillsRegistryEntry> HEALING_SKILL_ENTRY_ID = ResourceKey.create(ExampleModRegistries.MAGIC_SKILLS_SYNCED_REGISTRY_KEY, ExampleMod.id("healing_skill"));1
Доступ до даних реєстру
Динамічні реєстри завантажуються разом зі світом; отримати до них доступ можна через клас RegistryAccess, використовуючи ключ вашого реєстру. Екземпляри RegistryAccess можна отримати з багатьох класів, найпоширенішими з яких є MinecraftServer, ServerLevel, ClientLevel, Entity тощо.
ВАЖЛИВО
Під час доступу до екземпляра RegistryAccess із класу, призначеного виключно для клієнтської частини, як-от, ClientLevel, доступні лише синхронізовані реєстри.
Отримання запису реєстру
До реєстрів можна отримати доступ за допомогою методу lookup класу RegistryAccess, який повертає Optional<Registry<T>>, де T — це тип реєстру.
java
Optional<Registry<MagicSkillsRegistryEntry>> registry = registryAccess.lookup(ExampleModRegistries.MAGIC_SKILLS_SYNCED_REGISTRY_KEY);1
Отримання конкретних записів
Доступ до конкретних записів можна отримати за допомогою методу get класу RegistryAccess, який повертає Optional<Holder.Reference<T>>, де T — це тип реєстру.
java
ResourceKey<MagicSkillsRegistryEntry> HEALING_SKILL_ENTRY_ID = ResourceKey.create(ExampleModRegistries.MAGIC_SKILLS_SYNCED_REGISTRY_KEY, ExampleMod.id("healing_skill"));
Optional<Holder.Reference<MagicSkillsRegistryEntry>> entry = registryAccess.get(HEALING_SKILL_ENTRY_ID);
entry.ifPresent(magicSkillRef -> {
MagicSkillsRegistryEntry magicSkill = magicSkillRef.value();
// Other logic to reduce player's mana and running the function
serverPlayer.sendOverlayMessage(Component.literal("Used %s Magical Skill, Mana Reduced By %d".formatted(magicSkill.name(), magicSkill.manaCost())));
});1
2
3
4
5
6
7
2
3
4
5
6
7
Ознайомтеся з ID запису, щоб дізнатися, як отримати HEALING_SKILL_ENTRY_ID.
У нашому випадку ми можемо використати цей метод, щоб отримати запис про магічну здібність, яку користувач застосував на сервері, а потім видобути поле onUseMcFunction для виконання mcfunction.
Перебір усіх записів
Записи реєстру ітерувати можна для різних цілей, наприклад, для заповнення елементів інтерфейсу. У нашому випадку ми можемо використати цей метод, щоб заповнити екран власними віджетами, як-от:
java
registry.ifPresent(reg -> {
int y = 50;
for (MagicSkillsRegistryEntry skill : reg) {
MagicSkillWidget widget = new MagicSkillWidget(skill, font, 40, y, 80, 20);
this.addRenderableWidget(widget);
y += 30;
}
});1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Спеціальний екран, що заповнюється даними з реєстру

Теґи для спеціальних записів реєстру
Теґи — це спосіб групування кількох записів. Наприклад, ми можемо створити теґи на кшталт attack та defense, щоб згрупувати схожі магічні вміння.
Наприклад, теґ атаки буде визначено в data/example-mod/tags/example-mod/magic_skills_registry/attacking_skills.json:
json
{
"values": [
"example-mod:blast_skill",
"example-mod:magic_missile_skill"
]
}1
2
3
4
5
6
2
3
4
5
6
Використання теґів у коді
Створіть ключ теґу, щоб перевірити, чи містить цей теґ записи.
java
public static final TagKey<MagicSkillsRegistryEntry> ATTACKING_SKILLS_TAG_KEY = TagKey.create(ExampleModRegistries.MAGIC_SKILLS_SYNCED_REGISTRY_KEY, ExampleMod.id("attacking_skills"));
public static <T> boolean isPresentInMyTag(RegistryAccess registryAccess, ResourceKey<T> entryId, TagKey<T> tagKey) {
return registryAccess.get(entryId).map(reference -> reference.is(tagKey)).orElse(false);
}1
2
3
4
5
2
3
4
5
Ми можемо використовувати цей метод, щоб перевірити, чи є вміння атаки.
java
boolean isAttackingSkill = isPresentInMyTag(registryAccess, entryId, ATTACKING_SKILLS_TAG_KEY);1

