====== 拼图 ====== 拼图非常适合用于地牢和村庄等高级结构,并且可以让您花费更多的时间在实际构建内容上,而不是将其与程序生成代码搞混。 可以找到带有完成代码的存储库 [[https://github.com/Draylar/jigsaw-example-mod|here]]. ==== 创建一个结构特征 ==== ''StructureFeature''是高级的''Feature'':它跟踪其位置和范围,并具有从结构文件中生成自身的能力((虽然您可以从''.nbt''中生成您的''StructureFeature''文件,大多数普通的StructureFeatures都简单地覆盖了给定的Piece类中的''generate''方法))。 如果有帮助,您可以将其视为''结构'' +''功能''。 我们需要为拼图生成的结构创建一个。 首先,创建一个扩展''StructureFeature ''的类((AbstractTempleFeature是另一种选择。它会自动将类似于现有庙宇的结构隔开-村庄也使用此逻辑。)) 特征命名约定为"结构名称" +"特征"; 一些典型的示例是''EndCityFeature'',''OceanRuinFeature''和''VillageFeature''。 //注意:虽然Feature是世界上生成的事物的专有名称,但我们将添加的内容称为Structure。 这是为了区分StructureFeature和标准Feature。// 我们将按原样保留构造函数。''Function >''参数是结构配置-如果您没有任何计划将其弄乱,则可以简单地将''DefaultFeatureConfig :: deserialize''传递给super: public ExampleFeature(Function, ? extends DefaultFeatureConfig> config) { super(config); } //shouldStartAt//回答问题"我应该在给定的块中开始生成吗?" ''VanillaTempleFeature''是vanilla提供的一个类,它为这个问题提供了答案:它可以确保每个结构与其他相同类型的结构隔开。 标准乡村功能使用相同的逻辑,但不是该类别的子级。 通过返回true,每个块都将具有您的功能。 这对于测试非常有用,因此我们现在将使用该路由。 @Override public boolean shouldStartAt(ChunkGenerator chunkGenerator, Random random, int x, int z) { return true; } //getName// 是您的结构的名称。 它用于几件事,包括: *序列化,并从一大块反序列化的结构 *找到您的结构 *标记您的结构 原版约定是适当大写的标题。''Igloo'',''Village''和''Fortress''都是有效的。((请勿使用''TranslatableText'':意图很强的时候,事情会崩溃的。)) @Override public String getName() { return "Example Feature"; } //getRadius//的确切用法仍在讨论中,但是(作为作者)我的猜测是它与块内的翻译有关。 半径为4的结构可以在块边界内移动,而半径为8的结构将始终居中。 这可能是错误的。 目前,半径应为结构的一半; 如果您的结构将占用多个块(或全部填充),则返回8。 @Override public int getRadius() { return 2; } 最后,//getStructureStartFactory//是您的StructureFeature的生成部分。 您必须返回一个工厂方法来创建新的StructureStarts -我们可以简单地通过方法引用构造函数。 我们的实现将如下所示,''ExampleStructureStart''是本教程的下一步: @Override public StructureStartFactory getStructureStartFactory() { return ExampleStructureStart::new; } 我们完成的''ExampleFeature''类: import com.mojang.datafixers.Dynamic; import net.minecraft.world.gen.chunk.ChunkGenerator; import net.minecraft.world.gen.feature.DefaultFeatureConfig; import net.minecraft.world.gen.feature.StructureFeature; import java.util.Random; import java.util.function.Function; public class ExampleFeature extends StructureFeature { public ExampleFeature(Function, ? extends DefaultFeatureConfig> config) { super(config); } @Override public boolean shouldStartAt(ChunkGenerator chunkGenerator, Random random, int x, int z) { return true; } @Override public StructureStartFactory getStructureStartFactory() { return ExampleStructureStart::new; } @Override public String getName() { return "Example Feature"; } @Override public int getRadius() { return 2; } } ==== 创建一个StructureStart类 ===== ''StructureStart''就像在世界上生成我们的结构的初始化阶段。 现在,使用构造函数创建一个简单的子类,该构造函数还将覆盖initialize: public class ExampleStructureStart extends StructureStart { ExampleStructureStart(StructureFeature feature, int x, int z, Biome biome, MutableIntBoundingBox box, int int_3, long seed) { super(feature, x, z, biome, box, int_3, seed); } @Override public void initialize(ChunkGenerator chunkGenerator, StructureManager structureManager, int x, int z, Biome biome) { } } 要了解这里发生的情况,我们必须深入研究拼图 (https://minecraft.gamepedia.com/Jigsaw_Block) and 结构方块(https://minecraft.gamepedia.com/Structure_Block). 结构放块是一种将结构保存到.nbt文件以供将来使用的简单方法。 拼图是结构块的组成部分,将多个结构组装成一个结构。 与普通的拼图游戏类似,结构的每一块都在拼图块处连接,就像拼图块中的连接楔子一样。 我们假设您熟悉保存结构-如果不熟悉,请先阅读结构块页面,然后再进行任何操作。 拼图菜单包含3个: *目标池 *附件类型 *转成 {{https://i.imgur.com/owaJ0k2.png|Blank Jigsaw}} 将其视为难题时,目标池就是您可以搜索的一组难题. 如果您共有10件,那么一个目标池可能有7件. 拼图是如何在此字段中指定的:"嗨,我希望B组的一块可以和我联系!" 就一个村庄而言,这可能是一条路,说:“给我房子!” 2个拼图的目标池不必匹配:请求者可以决定从中选择谁。 **不是**定义给定的//拼图块是什么类型,而是应该定义另一种类型///. 附件类型可以看作是目标池中更具体的过滤器-拼图只能连接到具有相同附件类型的其他拼图。 这就像拼图块上的连接器类型。 用法更加具体. 最后,“turns into”字段只是拼图找到匹配项后所替换的内容。 如果拼图位于您的鹅卵石地板内,它可能会变成原石. 这是一个示例实现:给定的拼图将从// tutorial:my_pool //结构池中绘制,查找具有// tutorial:any //类型的所有拼图,并在完成后变成原石。 {{https://i.imgur.com/f9tP2sv.png|Example Finished Jigsaw}} Our finalized structure will consist of multiple colored squares connecting to each other. It will have a white or a black square in the center, and orange, magenta, light blue, and lime squares branching off on the sides randomly. Here is the setup of our 2 initial squares: {{https://i.imgur.com/dVFADy8.png|Initial Squares}} This jigsaw will ask for any other jigsaw that: * is in the //tutorial:color_pool// target pool * has an attachment type of //tutorial:square_edge// It then turns into white concrete to match the rest of the platform. For demo purposes, we've made 2 starting platforms: one is white, and one is black. The only difference is what they turn into. We'll save these as structure files using structure blocks: {{https://i.imgur.com/31LAORw.png|Finalized Initial Squares}} For our randomized edge platforms, we've made 4 extra squares of different colors. Again, despite being used for a different purpose, the jigsaw construction is //the same// aside from the "turns into" field. {{https://i.imgur.com/OngxweJ.png|Colored Squares}} We now have 6 saved ''.nbt'' files. These can be found in our world save folder under ''generated'': {{https://i.imgur.com/ZKIoZT9.png|Saved NBT files}} For usage, we'll move these to ''resources/data/tutorial/structures'', where "tutorial" is your modid: {{https://i.imgur.com/kaiy84U.png|Moved NBT files}} The setup is complete! We now have 6 total squares. Let's briefly recap the goal: * have a white or black square selected as the center for our structure * have a pool of the 4 other colors * branch off from the center square with our 4 extra colors Let's head back to our ''ExampleStructureStart'' class. First, we'll need 2 Identifiers to label our 2 pools (black&white, 4 colors): private static final Identifier BASE_POOL = new Identifier("tutorial:base_pool"); private static final Identifier COLOR_POOL = new Identifier("tutorial:color_pool"); Remember: every jigsaw ends up searching through the color pool, but we still have a base pool! This is to keep our black & white squares out of the outside generated squares. It's also going to be our origin pool, where we randomly select 1 structure from to begin our generation. In a static block at the bottom of our class, we're going to register our structure pools using ''StructurePoolBasedGenerator.REGISTRY'': static { StructurePoolBasedGenerator.REGISTRY.add( new StructurePool( BASE_POOL, new Identifier("empty"), ImmutableList.of( Pair.of(new SinglePoolElement("tutorial:black_square"), 1), Pair.of(new SinglePoolElement("tutorial:white_square"), 1) ), StructurePool.Projection.RIGID ) ); StructurePoolBasedGenerator.REGISTRY.add( new StructurePool( COLOR_POOL, new Identifier("empty"), ImmutableList.of( Pair.of(new SinglePoolElement("tutorial:lime_square"), 1), Pair.of(new SinglePoolElement("tutorial:magenta_square"), 1), Pair.of(new SinglePoolElement("tutorial:orange_square"), 1), Pair.of(new SinglePoolElement("tutorial:light_blue_square"), 1) ), StructurePool.Projection.RIGID ) ); } 在这里,我们要注册2个池(基础和颜色),然后将它们各自的子级添加到它们中。 StructurePool构造函数如下: *池的注册表名称,与竖锯顶部的目标池相同 *@Draylar,如果您知道这是做什么的 *池元素列表 *池的投影类型 对于元素列表,我们添加池元素和整数的Pairs((com.mojang.datafixers.util))。 传递到元素的字符串是结构在数据目录中的位置,而int是元素在整个目标池中的权重。 对每个元素使用1可以确保每个元素被均匀地拾取. 投影是如何将池放置在世界上的。 刚性表示将直接按原样放置,而地形匹配则表示将其弯曲以位于地形顶部。 后者可能适合随地形变化的麦田结构,而前者则适合具有坚固地板的房屋。 现在,我们要做的就是在''initialize''方法中添加起始片段: @Override public void initialize(ChunkGenerator chunkGenerator, StructureManager structureManager, int x, int z, Biome biome) { StructurePoolBasedGenerator.addPieces(BASE_POOL, 7, ExamplePiece::new, chunkGenerator, structureManager, new BlockPos(x * 16, 150, z * 16), children, random); setBoundingBoxFromChildren(); } Identifier是可供选择的起始池,int是整个结构的大小(其中7为“7 squares out”),第3个参数是我们将在第二秒注册的零件的工厂. ==== 创作作品 ==== 这部分非常简单。 一块代表整个结构中的一个部分或元素。 您需要创建一个基本的计件类,稍后我们将进行注册: public class ExamplePiece extends PoolStructurePiece { ExamplePiece(StructureManager structureManager_1, StructurePoolElement structurePoolElement_1, BlockPos blockPos_1, int int_1, BlockRotation blockRotation_1, MutableIntBoundingBox mutableIntBoundingBox_1) { super(ExampleMod.EXAMPLE_PIECE, structureManager_1, structurePoolElement_1, blockPos_1, int_1, blockRotation_1, mutableIntBoundingBox_1); } public ExamplePiece(StructureManager manager, CompoundTag tag) { super(manager, tag, ExampleMod.EXAMPLE_PIECE); } } 其中''ExampleMod.EXAMPLE_PIECE''是对我们注册件的引用。 ==== 注册所有 ==== 我们需要将结构既注册为特征//和//还是结构特征,并注册我们的作品。 将结构注册为StructureFeature是可选的,并且用于将其保存到块中。 如果世界在您的结构加载过程中途中止,则在重新打开世界后对其进行注册将使其继续存在。 如果未将其注册到结构特征中,并且发生了这种情况,则该结构将中途停止(大多数情况下只会发生在较大的,多块宽的结构中)。 public static final StructureFeature EXAMPLE_FEATURE = Registry.register( Registry.FEATURE, new Identifier("tutorial", "example_feature"), new ExampleFeature(DefaultFeatureConfig::deserialize) ); public static final StructureFeature EXAMPLE_STRUCTURE_FEATURE = Registry.register( Registry.STRUCTURE_FEATURE, new Identifier("tutorial", "example_structure_feature"), EXAMPLE_FEATURE ); public static final StructurePieceType EXAMPLE_PIECE = Registry.register( Registry.STRUCTURE_PIECE, new Identifier("tutorial", "example_piece"), ExamplePiece::new ); ==== 生成我们的结构 ==== 最后,我们必须生成我们的结构。 将其添加到每个生物群系的一个基本示例是: Registry.BIOME.forEach(biome -> { biome.addFeature(GenerationStep.Feature.RAW_GENERATION, Biome.configureFeature(EXAMPLE_FEATURE, new DefaultFeatureConfig(), Decorator.NOPE, DecoratorConfig.DEFAULT)); biome.addStructureFeature(EXAMPLE_FEATURE, new DefaultFeatureConfig()); }); === 完成! === 如您所见,我们在中心有一个白色正方形,框在边缘之外。 请注意,此屏幕截图中的半径已增加到14,而不是本教程中使用的7。 {{https://i.imgur.com/qndZzZu.png|搞定}}