Всем привет! Пришло время применить все созданные блоки на практике. Структуры это одна из самых интересных частей любых песочниц. Все мы с радостью заваливаем в пустынный замок, для четырёх сундуков и девяти блоков динамита, и бегаем по шахтам, аванпостам, деревням и т.д. О структурах той же террарии я и вовсе промолчу, уж слишком они круты.
Блоков у нас уже прилично, но сперва немного теории. Структуры по своей реализации в чем-то похожи на руду. Нам нужно зарегистрировать структуру в игре, а потом добавить её как особенность для определённых (или всех) биомов. Но, конечно же, всё будет несколько сложнее, чем с простыми блоками. На то она и структура.
Но всё же стоит уточнить, что несмотря на схожесть в реализации, игра разделяет генерируемые элементы биомов на особенности и структуры (они тоже особенности, но сложнее устроены). Для примера: хижина ведьмы – структура, а пустынный колодец – особенность. Т.е. особенность – единый элемент, это дерево, кактус, колодец, а структуры состоят из частей (храмы, деревни). Некоторые из одной, как всё та же хижина ведьмы, а некоторые имеют множество частей и вариантов (затонувшие корабли, иглу). Но про это позже. Сейчас мы займёмся именно особенностью, по аналогии с колодцем.
Советую сперва пройти урок про генерацию руды. Некоторые моменты будут максимально сокращены, так как в том уроке они уже были описаны.
Создаём класс особенности
По аналогии с оригинальными структурам, создадим отдельные папки для генерации, и для самих структур: родительская_папка_исходного_кода/world/. Новой особенностью станет Солнечный алтарь, а потому его класс получает имя: SunAltarFeature.java.
Пишем туда такой вот код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
package mod.astler.tutorial_mod_gs.world; import com.mojang.datafixers.Dynamic; import mod.astler.tutorial_mod_gs.init.ModBlocks; import net.minecraft.block.BlockState; import net.minecraft.block.StairsBlock; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IWorld; import net.minecraft.world.gen.ChunkGenerator; import net.minecraft.world.gen.GenerationSettings; import net.minecraft.world.gen.feature.Feature; import net.minecraft.world.gen.feature.NoFeatureConfig; import java.util.Random; import java.util.function.Function; public class SunAltarFeature extends Feature<NoFeatureConfig> { private final BlockState sun_bricks = ModBlocks.SUN_BRICKS.getDefaultState(); private final BlockState sun_bricks_stairs_1 = ModBlocks.SUN_BRICKS_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.NORTH); private final BlockState sun_bricks_stairs_2 = ModBlocks.SUN_BRICKS_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.EAST); private final BlockState sun_bricks_stairs_3 = ModBlocks.SUN_BRICKS_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.WEST); private final BlockState sun_bricks_stairs_4 = ModBlocks.SUN_BRICKS_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.SOUTH); private final BlockState table_block = ModBlocks.TABLE_BLOCK.getDefaultState(); private final BlockState sun_dust_glass = ModBlocks.SUN_DUST_GLASS.getDefaultState(); public SunAltarFeature(Function<Dynamic<?>, ? extends NoFeatureConfig> configFactoryIn) { super(configFactoryIn); } public boolean place(IWorld worldIn, ChunkGenerator<? extends GenerationSettings> generator, Random rand, BlockPos pos, NoFeatureConfig config) { for (pos = pos.up(); worldIn.isAirBlock(pos) && pos.getY() > 2; pos = pos.down()) { } System.out.println("placed sun_altar: x = " + pos.getX() + " y = " + pos.getY() + " z = " + pos.getZ()); for (int l = 0; l <= 5; ++l) { for (int l1 = -4; l1 <= 4; ++l1) { for (int k = -4; k <= 4; ++k) { worldIn.setBlockState(pos.add(l1, l, k), this.sun_bricks, 2); } } } return true; } } |
Окей. Разберём этот код. Конструктор принимает один параметр. Это функция для дополнения генерации структуры. Подобные функции указаны для цветов, деревьев, озёр, но так как у нас устойчивая структура, то никаких дополнительных параметров не нужно. Поэтому немного позже, при регистрации структуры, мы укажем пустой конфиг. Так же я уже объявил несколько глобальных переменных, которые содержат BlockState используемых блоков.
Функция place и отвечает за генерацию всего и вся. Сперва мы проверяем выбранный блок, чтобы структура не оказалась полностью в воздухе. А после этого генерируем что-то похожее на кубик при помощи трёх циклов (т.к. у нас три плоскости).
Регистрируем особенность
Если вы уже создавали блоки или предметы, то у вас уже должен быть файлик для регистрации всего этого добра. В уроках я использовал файл ModEventSubscriber.java. В этот раз мы снова к нему вернемся и добавим новую функцию:
1 2 3 4 5 6 7 8 |
@SubscribeEvent public static void registerFeatures(RegistryEvent.Register<Feature<?>> event) { IForgeRegistry<Feature<?>> registry = event.getRegistry(); registry.registerAll( <em>setup</em>(new SunAltarFeature(NoFeatureConfig::<em>deserialize</em>), "sun_altar") ); } |
Она работает по той же логике, что и регистрация блоков и предметов. Так что ничего нового тут нет. Параметр принимаемый классом SunAltarFeature – тот самый конфиг, которого у нас нет, что мы и указали.
И всё, вы добавили новую особенность и игру, правда, найти её пока не выйдет, ведь генерацию для неё мы пока не указали.
Генерация особенности
Вот тут идёт прямая отсылка к уроку о генерации руды. Там мы создавали отдельный класс для обработки событий генерации, а также прописали руду для всех биомов. Он назывался ModEventGenSubscriber. Может прозвучит немного странно сейчас, но руда так же была особенностью. Я тогда об этом сказал, но не останавливался для пояснения. Суть в том, что любые декораторы биомов, это особенности. Так что и руды, и особые виды камня, и даже пещеры – все одного рода. Просто имеют разные параметры генерации, и свойства. К примеру руды – подземные особенности, а есть еще наземные и подводные. Короче, подробнее нужно брать частные случаи, а пока просто запомним – всё, что мы видим в биоме – его особенности.
Поэтому в функцию onInitBiomesGen пишем для всех (или для определённых) биомов такую вот строку:
1 |
biome.addFeature(GenerationStage.Decoration.<em>SURFACE_STRUCTURES</em>, ModStructures.<em>SUN_ALTAR</em>.func_225566_b_(IFeatureConfig.<em>NO_FEATURE_CONFIG</em>).func_227228_a_(Placement.<em>CHANCE_HEIGHTMAP</em>.func_227446_a_(new ChanceConfig(10)))); |
(она одна, просто длинная)
Так вот. Тут мы добавляем особенность для поверхности (SURFACE_STRUCTURES), указываем ссылку на объект нашей структуры (опять же по аналогии с блоками):
1 2 3 4 5 6 7 8 9 10 11 |
package mod.astler.tutorial_mod_gs.world; import mod.astler.tutorial_mod_gs.TutorialGSMod; import mod.astler.tutorial_mod_gs.world.SunAltarFeature; import net.minecraftforge.registries.ObjectHolder; @ObjectHolder(TutorialGSMod.<em>MODID</em>) public class ModStructures { @ObjectHolder("sun_altar") public static SunAltarFeature <em>SUN_ALTAR</em>; } |
И особого внимания заслуживает конфиг расположения, а именно класс ChanceConfig. Он принимает целое число, в зависимости от которого будет зависеть генерация нашей особенности в мире. У пустынного колодца это 1000, а я указал 10. Это значит, что наших блоков сейчас будет очень и очень много. Запускаем и тестируем.

Класс! Как можно увидеть, кубики есть и их много. Теперь запаримся и создадим вместо нелепой коробки что-то более органичное. Мы все же алтарь делали!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
package mod.astler.tutorial_mod_gs.world; import com.mojang.datafixers.Dynamic; import mod.astler.tutorial_mod_gs.init.ModBlocks; import net.minecraft.block.*; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IWorld; import net.minecraft.world.gen.ChunkGenerator; import net.minecraft.world.gen.GenerationSettings; import net.minecraft.world.gen.feature.Feature; import net.minecraft.world.gen.feature.NoFeatureConfig; import java.util.Random; import java.util.function.Function; public class SunAltarFeature extends Feature<NoFeatureConfig> { private final BlockState sun_bricks = ModBlocks.SUN_BRICKS.getDefaultState(); private final BlockState sun_bricks_stairs_1 = ModBlocks.SUN_BRICKS_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.NORTH); private final BlockState sun_bricks_stairs_2 = ModBlocks.SUN_BRICKS_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.EAST); private final BlockState sun_bricks_stairs_3 = ModBlocks.SUN_BRICKS_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.WEST); private final BlockState sun_bricks_stairs_4 = ModBlocks.SUN_BRICKS_STAIRS.getDefaultState().with(StairsBlock.FACING, Direction.SOUTH); private final BlockState table_block = ModBlocks.TABLE_BLOCK.getDefaultState(); private final BlockState sun_dust_glass = ModBlocks.SUN_DUST_GLASS.getDefaultState(); private final BlockState cobblestone = Blocks.COBBLESTONE.getDefaultState(); private final BlockState sun_lamp = ModBlocks.SUN_LAMP.getDefaultState(); public SunAltarFeature(Function<Dynamic<?>, ? extends NoFeatureConfig> configFactoryIn) { super(configFactoryIn); } public boolean place(IWorld worldIn, ChunkGenerator<? extends GenerationSettings> generator, Random rand, BlockPos pos, NoFeatureConfig config) { for (pos = pos.up(); worldIn.isAirBlock(pos) && pos.getY() > 2; pos = pos.down()) { } System.out.println("placed sun_altar: x = " + pos.getX() + " y = " + pos.getY() + " z = " + pos.getZ()); if (worldIn.getBlockState(pos).isSolid() || worldIn.getBlockState(pos).getMaterial().isLiquid()) { int maxX = 2; int maxZ = 2; int minX = -2; int minZ = -2; for (int l1 = minX; l1 <= maxX; ++l1) { for (int k = minZ; k <= maxZ; ++k) { worldIn.setBlockState(pos.add(l1, 0, k), this.sun_bricks, 2); } } for (int l1 = minX + 1; l1 <= maxX - 1; ++l1) { for (int k = minZ + 1; k <= maxZ - 1; ++k) { worldIn.setBlockState(pos.add(l1, 1, k), this.sun_bricks, 2); } } for (int l1 = minX; l1 <= maxX; ++l1) { for (int k = minZ; k <= maxZ; ++k) { boolean b = Math.abs(l1) == Math.abs(maxX) && Math.abs(k) == Math.abs(maxZ); if (!b) worldIn.setBlockState(pos.add(l1, 5, k), this.sun_dust_glass, 2); } } for (int l1 = minX + 1; l1 <= maxX - 1; ++l1) { for (int k = minZ + 1; k <= maxZ - 1; ++k) { worldIn.setBlockState(pos.add(l1, 6, k), this.sun_dust_glass, 2); } } for (int iy = 1; iy <= 4; iy++) for (int l1 = minX; l1 <= maxX; l1 += 4) { for (int k = minZ; k <= maxZ; k += 4) { worldIn.setBlockState(pos.add(l1, iy, k), this.sun_dust_glass, 2); } } worldIn.setBlockState(pos.add(-1, 1, -2), this.sun_bricks_stairs_4, 2); worldIn.setBlockState(pos.add(0, 1, -2), this.sun_bricks_stairs_4, 2); worldIn.setBlockState(pos.add(1, 1, -2), this.sun_bricks_stairs_4, 2); worldIn.setBlockState(pos.add(-1, 1, 2), this.sun_bricks_stairs_1, 2); worldIn.setBlockState(pos.add(0, 1, 2), this.sun_bricks_stairs_1, 2); worldIn.setBlockState(pos.add(1, 1, 2), this.sun_bricks_stairs_1, 2); worldIn.setBlockState(pos.add(2, 1, -1), this.sun_bricks_stairs_3, 2); worldIn.setBlockState(pos.add(2, 1, 0), this.sun_bricks_stairs_3, 2); worldIn.setBlockState(pos.add(2, 1, 1), this.sun_bricks_stairs_3, 2); worldIn.setBlockState(pos.add(-2, 1, -1), this.sun_bricks_stairs_2, 2); worldIn.setBlockState(pos.add(-2, 1, 0), this.sun_bricks_stairs_2, 2); worldIn.setBlockState(pos.add(-2, 1, 1), this.sun_bricks_stairs_2, 2); worldIn.setBlockState(pos.add(0, 2, 0), this.table_block, 2); worldIn.setBlockState(pos.add(0, 5, 0), this.sun_lamp, 2); worldIn.setBlockState(pos.add(-1, 5, 0), this.sun_dust_glass, 2); worldIn.setBlockState(pos.add(1, 5, 0), this.sun_dust_glass, 2); worldIn.setBlockState(pos.add(0, 5, -1), this.sun_dust_glass, 2); worldIn.setBlockState(pos.add(0, 5, 1), this.sun_dust_glass, 2); for (int ii = minX; ii <= maxX; ++ii) { for (int ij = minZ; ij <= maxZ; ++ij) { worldIn.setBlockState(pos.add(ii, -1, ij), cobblestone, 3); boolean b = Math.abs(ii) == Math.abs(maxX) && Math.abs(ij) == Math.abs(maxZ); System.out.println("b is " + b + " and = " + ii + " and = " + ij); if (b) { int innerY = -2; while ((worldIn.isAirBlock(pos.add(ii, innerY, ij)) || worldIn.getBlockState(pos.add(ii, innerY, ij)).getMaterial().isLiquid()) && pos.add(ii, innerY, ij).getY() > 1) { worldIn.setBlockState(pos.add(ii, innerY, ij), Blocks.COBBLESTONE.getDefaultState(), 2); --innerY; } } } } } else { return false; } return true; } } |
Так что я немного переписал код генерации большого куба в код генерации такой вот беседки со столиком и лампой.

Вышло вполне симпатично, так что на этом пока и остановимся. По поводу размещения блоков ничего сложного нет. Берём блок для генерации структуры центром, который будет своего рода нулевой координатой для всех остальных блоков, а потом посредством добавления к этой координате различных чисел – строим что-то классное!
Итак, на этом у меня всё! Спасибо за внимание! Их слишком много, зато ночью красиво 😀

Пингбэк: Создание модов для Minecraft 1.15 – GeekStand