Приветствую моддер, я не нашел в интернете подробного гайда по данной теме, поэтому эта статья — способ обобщить мой опыт и поделиться им с другими. В основном статья предназначена для начинающих, но будет не лишним и для тех кто хочет напомнить себе работу со структурами при моддинге. Позже я буду делать её более подробной, либо писать дополнения к этой статье.
-
Стек: Minecraft 1.20.1, Forge 47.0.+, Litematica, IntelliJ IDEA.
Подготовка
Прежде чем переходить к написанию кода начнем с подготовки самого строения. И для этого есть несколько методов:
-
Метод 1: Structure Block. Хорошо для начала.
-
Структурный блок подходит для небольших структур, всё из-за главной проблемы: Ограничения по размеру сохранения до 64x256x64 блоков, где первое число — это ширина (ось X), второе — высота (ось Y), и третье — глубина (ось Z).
-
-
**Метод 2: Litematica ** ( на котором мы остановимся). Что это? Специальный инструмент для строительства, редактирования, создания и экспорта схематик в формате
.litematicили ванильномnbt.-
Рабочий процесс: 1. Устанавливаем Litematica (и MaLiLib) как обычный мод. 2. Строим в креативном мире нашу мега-структуру. 3. С помощью инструментов Litematica выделяем ее и сохраняем в формате
.litematic. 4. Ключевой шаг: Экспорт, необходимо зайти в меню сохраненных схем, выбрать нужную и в нижней панели выбрать ванильный формат (nbt). -
Финальный шаг: Копируем полученный .nbt файл из папки
chematicsв корне самой игры, вставляем в ресурсы мода по путиsrc/main/resources/data/modid/structures/my_test_example.nbt.
-
Начало
Для того, чтобы наша структура работала и генерировалась в мире, нам понадобится подключить Json файлы, настроить (об этом потом) их содержимое к нашему nbt файлу и зарегистрировать в java класс — так происходит работа с привычным и в какой-то степени классическим Jigsaw-подходом.
-
Json файлы необходимые в этом случае:
template_pool.jsonс одним элементом (single_pool_element), который указывает на наш файл .nbt иstructure.jsonиstructure_set.jsonПоследние два файла содержат информацию о структуре (биом для генерации, частота генерации, уникальный номер, расположение к рельефу и т.д). Jigsaw отличный выбор для небольших структур, но есть и минусы, особенно с большими структурами. В моем случае minecraft даже не смог сгенерировать строение. Анализ проблемы: Этот метод метод был разработан не для размещения одного гигантского объекта, а для композиции структуры из множества мелких и средних частей. Например, деревня состоит из 10-20 небольших.nbt(дома, дороги, колодцы), и система эффективно справляется с их последовательным размещением. Когда Jigsaw-система решает разместить элемент из пула (например, черезminecraft:single_pool_element), она загружает весь указанный.nbtфайл в память, чтобы вставить его в мир. Отсутствие внятных ошибок: Если вы ошиблись, игра в 99% случаев просто промолчит. В логах не будет ошибки «файл не найден». Команда /locate будет говорить, что такой структуры не существует, и вы будете часами искать опечатку. Это крайне болезненный процесс отладки.
Решение: Программная генерация через свой класс Structure
Вместо того чтобы полагаться на JSON-конфигурацию для загрузки .nbt, мы напишем свой собственный Java-класс, который будет управлять процессом генерации. Это дает нам полный контроль.
Шаг 1: Создаем свой класс Structure
-
Создайте файл
MyGiantStructure.java, который наследуется от net.minecraft.world.level.levelgen.structure.Structure.
public class MyGiantStructure extends Structure { // Кодек для сериализации/десериализации нашей структуры public static final Codec<MyGiantStructure> CODEC = simpleCodec(MyGiantStructure::new); public MyGiantStructure(StructureSettings settings) { super(settings); } // Это сердце нашего метода! @Override public Optional<GenerationStub> findGenerationPoint(GenerationContext context) { // Логика поиска подходящего места для спавна. // Для простоты можно просто выбрать центр чанка. BlockPos centerOfChunk = context.chunkPos().getMiddleBlockPosition(0); // Возвращаем точку генерации return Optional.of(new GenerationStub(centerOfChunk, (builder) -> { // Здесь мы будем вызывать саму генерацию this.generate(builder, context); })); } // Метод, который будет размещать нашу структуру в мире private void generate(StructurePiecesBuilder builder, GenerationContext context) { BlockPos pos = builder.getCenter(); // Получаем позицию из GenerationStub ResourceLocation location = new ResourceLocation(MyMod.MODID, "my_giant_castle"); // Добавляем "кусок" нашей структуры. Так как она одна, кусок будет один. builder.addPiece(new MyGiantStructurePiece(context.structureTemplateManager(), location, pos)); } @Override public StructureType<?> type() { return ModStructureTypes.MY_GIANT_STRUCTURE.get(); // Ссылка на наш зарегистрированный тип } }
Шаг 2: Создаем класс «куска» структуры (StructurePiece).
-
Создайте MyGiantStructurePiece.java, который наследуется от TemplateStructurePiece. Этот класс будет отвечать за реальное размещение блоков из .nbt.
-
Логика здесь довольно стандартна, она просто загружает .nbt и размещает его.
public class MyGiantStructurePiece extends TemplateStructurePiece { public MyGiantStructurePiece(StructureTemplateManager manager, ResourceLocation location, BlockPos pos) { super(ModStructurePieceTypes.MY_GIANT_PIECE.get(), 0, manager, location, location.toString(), new StructurePlaceSettings(), pos); } // Конструктор для загрузки из NBT public MyGiantStructurePiece(ServerLevel level, CompoundTag tag) { super(ModStructurePieceTypes.MY_GIANT_PIECE.get(), tag, level.getServer().getStructureManager(), (location) -> new StructurePlaceSettings()); } @Override protected void handleDataMarker(String function, BlockPos pos, ServerLevelAccessor level, RandomSource random, BoundingBox sbox) { // Можно оставить пустым, если у вас нет дата-блоков } }
Шаг 3: Регистрация всего этого в Forge.
-
Покажите, как теперь выглядит регистрация StructureType (он ссылается на кодек нашего нового класса).
-
Добавьте регистрацию StructurePieceType.
// В классе ModStructureTypes.java public static final RegistryObject<StructureType<MyGiantStructure>> MY_GIANT_STRUCTURE = STRUCTURE_TYPES.register("my_giant_structure", () -> () -> MyGiantStructure.CODEC); // В отдельном классе ModStructurePieceTypes.java public static final DeferredRegister<StructurePieceType> PIECE_TYPES = ... public static final RegistryObject<StructurePieceType> MY_GIANT_PIECE = PIECE_TYPES.register("my_giant_piece", () -> MyGiantStructurePiece::new);
Конфигурация в JSON
Теперь нам хватит двух файлов. template_pool.json — удаляем, он больше не нужен.
structure.json: Становится тривиальным. Его единственная задача — указать на наш зарегистрированный тип структуры и задать биомы/категорию спавна.
"type": "mymod:my_giant_structure", "biomes": "#minecraft:is_overworld", "step": "surface_structures", "spawn_overrides": {} }
structure_set.json: Остается таким же, он все еще отвечает за частоту и расстояние между структурами.
Финал
Основная настройка структуры подходит к концу, и чтобы подытожить и помочь я продемонстрирую как выглядят каркасы java файлов.
*MyGiantStructure.java. Этот класс решает, где разместить структуру, и инициирует процесс строительства.
// src/main/java/com/yourname/mymod/world/structure/MyGiantStructure.java package com.yourname.mymod.world.structure; import com.mojang.serialization.Codec; import com.yourname.mymod.MyMod; // Замените на ваш главный класс мода import com.yourname.mymod.world.ModStructureTypes; // Замените на ваш класс регистрации типов структур import net.minecraft.core.BlockPos; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureType; import net.minecraft.world.level.levelgen.structure.pieces.StructurePiecesBuilder; import java.util.Optional; /** * Главный класс нашей структуры. Он выступает в роли "архитектора". * Его задача - найти подходящее место для генерации и дать команду на начало "строительства". */ public class MyGiantStructure extends Structure { /** * Codec - это механизм сериализации/десериализации от Mojang. * Он нужен, чтобы Minecraft мог сохранять и загружать информацию о нашей структуре. * simpleCodec использует конструктор класса для создания экземпляра. */ public static final Codec<MyGiantStructure> CODEC = simpleCodec(MyGiantStructure::new); /** * Конструктор. Принимает настройки (биомы, шаг генерации и т.д.), * которые обычно загружаются из structure.json. */ public MyGiantStructure(StructureSettings settings) { super(settings); } /** * Это сердце нашего класса. Метод вызывается для каждого чанка, чтобы определить, * можно ли здесь начать генерацию нашей структуры. * @return Optional.of(...) если место подходит, Optional.empty() если нет. */ @Override public Optional<GenerationStub> findGenerationPoint(GenerationContext context) { // Находим подходящую точку для старта. Для простоты возьмем центр чанка. // Вы можете добавить сюда сложную логику, например, проверку высоты ландшафта. BlockPos startPos = new BlockPos(context.chunkPos().getMinBlockX(), 90, context.chunkPos().getMinBlockZ()); // Возвращаем "заглушку" для генерации. Это "обещание" построить структуру. // Сама генерация происходит в лямбда-функции. return Optional.of(new GenerationStub(startPos, (piecesBuilder) -> { this.generatePieces(piecesBuilder, context, startPos); })); } /** * Этот метод создает и добавляет "куски" (pieces) нашей структуры в мир. * В нашем случае кусок всего один. */ private void generatePieces(StructurePiecesBuilder piecesBuilder, GenerationContext context, BlockPos startPos) { // Указываем путь к нашему .nbt файлу. // Он должен лежать в `src/main/resources/data/mymod/structures/my_giant_castle.nbt` ResourceLocation location = new ResourceLocation(MyMod.MODID, "my_giant_castle"); // Создаем и добавляем единственный "кусок" нашей структуры, передавая ему все необходимые данные. piecesBuilder.addPiece(new MyGiantStructurePiece( context.structureTemplateManager(), location, startPos )); } /** * Возвращает зарегистрированный тип этой структуры. * Это нужно, чтобы Minecraft мог идентифицировать её. */ @Override public StructureType<?> type() { // ModStructureTypes.MY_GIANT_STRUCTURE - это RegistryObject, который вы создадите в отдельном классе. return ModStructureTypes.MY_GIANT_STRUCTURE.get(); } }
*MyGiantStructurePiece.java. Этот класс — «строительная бригада». Он берет конкретный .nbt файл и размещает его блоки в мире.
// src/main/java/com/yourname/mymod/world/structure/MyGiantStructurePiece.java package com.yourname.mymod.world.structure; import com.yourname.mymod.world.ModStructurePieceTypes; // Замените на ваш класс регистрации типов кусков import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.RandomSource; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.TemplateStructurePiece; import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; /** * Класс-"кусок" нашей структуры. Он отвечает за фактическое размещение блоков из .nbt файла. * Наследуется от TemplateStructurePiece, который содержит всю логику для работы с шаблонами. */ public class MyGiantStructurePiece extends TemplateStructurePiece { /** * Конструктор, который мы вызываем при первоначальной генерации мира. * @param templateManager Менеджер для загрузки .nbt файлов. * @param location Путь к нашему .nbt файлу. * @param pos Позиция, где будет размещена структура. */ public MyGiantStructurePiece(StructureTemplateManager templateManager, ResourceLocation location, BlockPos pos) { // Вызываем конструктор родительского класса, передавая все необходимые параметры. super( ModStructurePieceTypes.MY_GIANT_PIECE.get(), // Зарегистрированный тип этого "куска". 0, // genDepth, глубина генерации (для Jigsaw). templateManager, location, // ResourceLocation нашего .nbt location.toString(), // Имя шаблона, можно использовать то же самое. new StructurePlaceSettings(), // Настройки размещения (поворот, зеркалирование и т.д.). pos // Позиция. ); } /** * Второй конструктор. Он необходим для загрузки структуры из сохраненного мира. * Minecraft не генерирует структуры заново при загрузке, а считывает их из NBT-данных чанка. * @param serverLevel Мир * @param tag NBT-данные этого "куска" */ public MyGiantStructurePiece(net.minecraft.server.level.ServerLevel serverLevel, CompoundTag tag) { super(ModStructurePieceTypes.MY_GIANT_PIECE.get(), tag, serverLevel.getServer().getStructureManager(), (location) -> new StructurePlaceSettings()); } /** * Этот метод вызывается для обработки специальных "блоков данных" (Data Structure Blocks) в вашем .nbt. * Они позволяют выполнять команды или спавнить сущностей. * Если вы их не используете, можно оставить этот метод пустым. */ @Override protected void handleDataMarker(String function, BlockPos pos, ServerLevelAccessor level, RandomSource random, BoundingBox sbox) { // Пример: // if (function.equals("spawn_zombie")) { // level.addFreshEntity(new Zombie(level.getLevel())); // } } }
Не забудьте добавить «регистрацию» в главный файл mods.toml:
-
Если бы у вас был замок и, скажем, маленькая хижина (small_hut), ваш mods.toml выглядел бы так:
# ... (остальная часть файла) [[features]] type = "minecraft:structure" id = "mymod:my_giant_castle" [[features]] type = "minecraft:structure" id = "mymod:small_hut"
Без этого блока Forge при запуске просто не знает, что ему нужно заглянуть в папку data/mymod/worldgen/structure и поискать там файлы для загрузки. Он проигнорирует их.
Заключение
Путь от идеи до первого сгенерированного замка оказался куда длиннее и извилистее, чем я ожидал. Сначала я уперся в стену производительности стандартного Jigsaw-метода. Потом, написав, как мне казалось, идеальный код, я часами искал проблему, которая крылась не в сложной логике Java, а в одной-единственной забытой строчке в mods.toml.
Этот опыт научил меня двум вещам. Во-первых, для нестандартных задач нужны нестандартные подходы. Программная генерация через собственный класс Structure — это именно такой подход, дающий гибкость и производительность там, где пасуют стандартные конфигурации. Во-вторых, самый важный навык моддера — это не столько умение писать код, сколько умение его отлаживать и методично искать причину, когда что-то идет не так.
Надеюсь, мой опыт сэкономит вам несколько часов (или даже дней) отладки и покажет, что даже самые большие и сложные задачи решаемы, если подходить к ним системно.
ссылка на оригинал статьи https://habr.com/ru/articles/937976/
Добавить комментарий