🇨🇳 中文 (Chinese - China)
🇨🇳 中文 (Chinese - China)
外观
🇨🇳 中文 (Chinese - China)
🇨🇳 中文 (Chinese - China)
外观
本页面基于这个版本编写:
1.21.4
前提
首先,请确保你已完成 Datagen 设置 。
首先,我们需要创建 ModelProvider。 创建一个 extends FabricModelProvider 类。 实现两个抽象方法:generateBlockStateModels 和 generateItemModels。 最后,创建一个与 super 匹配的构造函数。
public class FabricDocsReferenceModelProvider extends FabricModelProvider {
public FabricDocsReferenceModelProvider(FabricDataOutput output) {
super(output);
}
@Override
public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
}
@Override
public void generateItemModels(ItemModelGenerator itemModelGenerator) {
}
@Override
public String getName() {
return "FabricDocsReference Model Provider";
}
}在 onInitializeDataGenerator 方法中的 DataGeneratorEntrypoint 中注册此类。
@Override
public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
}对于方块模型,我们将主要关注 generateBlockStateModels 方法。 请注意参数 BlockStateModelGenerator blockStateModelGenerator——该对象将负责生成所有 JSON 文件。 以下是一些可用于生成所需模型的便捷示例:
blockStateModelGenerator.registerSimpleCubeAll(ModBlocks.STEEL_BLOCK);这是最常用的函数。 它为普通的 cube_all 方块模型生成一个 JSON 模型文件。 所有六个面都使用一个纹理,在本例中我们使用 steel_block。
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "fabric-docs-reference:block/steel_block"
}
}它还生成一个方块状态 JSON 文件。 由于我们没有方块状态属性(例如轴、朝向等),因此一个变体就够了,并且每次放置方块时都会使用。
{
"variants": {
"": {
"model": "fabric-docs-reference:block/steel_block"
}
}
}registerSingleton 方法根据你传入的 TexturedModel 和单个方块状态变体提供 JSON 模型文件。
blockStateModelGenerator.registerSingleton(ModBlocks.PIPE_BLOCK, TexturedModel.END_FOR_TOP_CUBE_COLUMN);该方法将为一个普通立方体生成模型,该立方体使用纹理文件 pipe_block 作为侧面,使用纹理文件 pipe_block_top 作为顶部和底部。
{
"parent": "minecraft:block/cube_column",
"textures": {
"end": "fabric-docs-reference:block/pipe_block_top",
"side": "fabric-docs-reference:block/pipe_block"
}
}TIP
如果您无法选择应该使用哪个 TextureModel,请打开 TexturedModel 类并查看 纹理映射!
blockStateModelGenerator.registerCubeAllModelTexturePool(ModBlocks.RUBY_BLOCK)
.stairs(ModBlocks.RUBY_STAIRS)
.slab(ModBlocks.RUBY_SLAB)
.fence(ModBlocks.RUBY_FENCE);另一个有用的方法是 registerCubeAllModelTexturePool:通过传入“基础方块”来定义纹理,然后附加具有相同纹理的“子方块”。 在这种情况下,我们传入了 RUBY_BLOCK,因此楼梯、台阶和栅栏将使用 RUBY_BLOCK 纹理。
你还可以附加一个 BlockFamily,它将为其所有“子项”生成模型。
public static final BlockFamily RUBY_FAMILY =
new BlockFamily.Builder(ModBlocks.RUBY_BLOCK)
.stairs(ModBlocks.RUBY_STAIRS)
.slab(ModBlocks.RUBY_SLAB)
.fence(ModBlocks.RUBY_FENCE)
.build();blockStateModelGenerator.registerCubeAllModelTexturePool(ModBlocks.RUBY_BLOCK).family(ModBlocks.RUBY_FAMILY);blockStateModelGenerator.registerDoor(ModBlocks.RUBY_DOOR);
blockStateModelGenerator.registerTrapdoor(ModBlocks.RUBY_TRAPDOOR);
// blockStateModelGenerator.registerOrientableTrapdoor(ModBlocks.RUBY_TRAPDOOR);门和活板门略有不同。 在这里,你必须制作三个新纹理——两个用于门,一个用于活板门。
ruby_door_top 用于上半部分,ruby_door_bottom 用于下半部分。registerDoor() 方法将为门的所有方向(打开和关闭)创建模型。assets/mod_id/textures/item/ 文件夹中。ruby_trapdoor。 它将被用于所有面。TrapdoorBlock 具有 FACING 属性,你可以使用注释掉的方法生成具有旋转纹理的模型文件 = 活板门将是“可定向的”。 否则,无论它面向哪个方向,看起来都会一样。在本节中,我们将创建具有橡木原木纹理的垂直橡木原木台阶模型。
点 2. - 6. 在名为 CustomBlockStateModelGenerator 的内部静态辅助类中声明。
创建一个具有 FACING 属性和 SINGLE 布尔属性的 VerticalSlab 方块,类似于 方块状态 教程中的那样。 SINGLE 将指示是否存在两块台阶。 然后你应该重写 getOutlineShape 和 getCollisionShape,以便正确渲染轮廓,并且方块具有正确的碰撞形状。
public static final VoxelShape NORTH_SHAPE = Block.createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 8.0);
public static final VoxelShape SOUTH_SHAPE = Block.createCuboidShape(0.0, 0.0, 8.0, 16.0, 16.0, 16.0);
public static final VoxelShape WEST_SHAPE = Block.createCuboidShape(0.0, 0.0, 0.0, 8.0, 16.0, 16.0);
public static final VoxelShape EAST_SHAPE = Block.createCuboidShape(8.0, 0.0, 0.0, 16.0, 16.0, 16.0);@Override
protected VoxelShape getSidesShape(BlockState state, BlockView world, BlockPos pos) {
boolean type = state.get(SINGLE);
Direction direction = state.get(FACING);
VoxelShape voxelShape;
if (type) {
switch (direction) {
case WEST -> voxelShape = WEST_SHAPE.asCuboid();
case EAST -> voxelShape = EAST_SHAPE.asCuboid();
case SOUTH -> voxelShape = SOUTH_SHAPE.asCuboid();
case NORTH -> voxelShape = NORTH_SHAPE.asCuboid();
default -> throw new MatchException(null, null);
}
return voxelShape;
} else {
return VoxelShapes.fullCube();
}
}
@Override
protected VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return this.getSidesShape(state, world, pos);
}
@Override
protected VoxelShape getCollisionShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return this.getSidesShape(state, world, pos);
}还要重写 canReplace() 方法,否则无法使台阶成为完整方块。
@Override
protected boolean canReplace(BlockState state, ItemPlacementContext context) {
Direction direction = state.get(FACING);
if (context.getStack().isOf(this.asItem()) && state.get(SINGLE)) {
if (context.canReplaceExisting()) {
return context.getSide().getOpposite() == direction;
}
}
return false;
}然后就大功告成了! 你现在可以去测试方块并将其放置在游戏中了。
现在,我们来创建一个父方块模型。 它可以确定尺寸、在手中或其他槽位中的位置以及纹理的 x 和 y 坐标。 建议使用诸如 Blockbench 之类的编辑器来完成此操作,因为手动制作非常繁琐。 它看起来应该是这样的:
{
"parent": "minecraft:block/block",
"textures": {
"particle": "#side"
},
"display": {
"gui": {
"rotation": [
30,
-135,
0
],
"translation": [
-1.5,
0.75,
0
],
"scale": [
0.625,
0.625,
0.625
]
},
"firstperson_righthand": {
"rotation": [
0,
-45,
0
],
"translation": [
0,
2,
0
],
"scale": [
0.375,
0.375,
0.375
]
},
"firstperson_lefthand": {
"rotation": [
0,
315,
0
],
"translation": [
0,
2,
0
],
"scale": [
0.375,
0.375,
0.375
]
},
"thirdperson_righthand": {
"rotation": [
75,
-45,
0
],
"translation": [
0,
0,
2
],
"scale": [
0.375,
0.375,
0.375
]
},
"thirdperson_lefthand": {
"rotation": [
75,
315,
0
],
"translation": [
0,
0,
2
],
"scale": [
0.375,
0.375,
0.375
]
}
},
"elements": [
{
"from": [
0,
0,
0
],
"to": [
16,
16,
8
],
"faces": {
"down": {
"uv": [
0,
8,
16,
16
],
"texture": "#bottom",
"cullface": "down",
"tintindex": 0
},
"up": {
"uv": [
0,
0,
16,
8
],
"texture": "#top",
"cullface": "up",
"tintindex": 0
},
"north": {
"uv": [
0,
0,
16,
16
],
"texture": "#side",
"cullface": "north",
"tintindex": 0
},
"south": {
"uv": [
0,
0,
16,
16
],
"texture": "#side",
"tintindex": 0
},
"west": {
"uv": [
0,
0,
8,
16
],
"texture": "#side",
"cullface": "west",
"tintindex": 0
},
"east": {
"uv": [
8,
0,
16,
16
],
"texture": "#side",
"cullface": "east",
"tintindex": 0
}
}
}
]
}请参阅 方块状态如何格式化(仅英文版) 来了解更多信息。 请注意 #bottom、#top、#side 关键字。 它们充当变量,可以由以此为父级的模型进行设置:
{
"parent": "minecraft:block/cube_bottom_top",
"textures": {
"bottom": "minecraft:block/sandstone_bottom",
"side": "minecraft:block/sandstone",
"top": "minecraft:block/sandstone_top"
}
}bottom 值将替换 #bottom 占位符,依此类推。 将其放在 resources/assets/mod_id/models/block/ 文件夹中。
我们还需要 Model 类的实例。 它代表我们模型内部的实际父方块模型。
public static final Model VERTICAL_SLAB = block("vertical_slab", TextureKey.BOTTOM, TextureKey.TOP, TextureKey.SIDE);
//helper method for creating Models
private static Model block(String parent, TextureKey... requiredTextureKeys) {
return new Model(Optional.of(Identifier.of(FabricDocsReference.MOD_ID, "block/" + parent)), Optional.empty(), requiredTextureKeys);
}
//helper method for creating Models with variants
private static Model block(String parent, String variant, TextureKey... requiredTextureKeys) {
return new Model(Optional.of(Identifier.of(FabricDocsReference.MOD_ID, "block/" + parent)), Optional.of(variant), requiredTextureKeys);
}block() 方法创建一个新的 Model,指向 resources/assets/mod_id/models/block/ 文件夹内的 vertical_slab.json 文件。 TextureKey 将“占位符”(#bottom、#top...) 表示为一个对象。
TextureMap 是干什么的? 它实际上提供了指向纹理的标识符。 从技术上讲,它的行为类似于普通映射——将 TextureKey(键)与 Identifier(值)关联起来。
你可以使用原版的,例如 TextureMap.all()(它将所有 TextureKey 与相同的标识符关联),或者创建一个新实例然后用 .put() 将键与值关联起来。
TIP
TextureMap.all() 将所有的 TextureKey 与相同的标识符关联起来,无论它们有多少!
因为我们想要用橡木原木纹理,但是有 BOTTOM、TOP 和 SIDE 的 TextureKey,所以我们需要创建一个新的。
public static TextureMap blockAndTopForEnds(Block block) {
return new TextureMap()
.put(TextureKey.TOP, ModelIds.getBlockSubModelId(block, "_top"))
.put(TextureKey.BOTTOM, ModelIds.getBlockSubModelId(block, "_top"))
.put(TextureKey.SIDE, ModelIds.getBlockModelId(block));
}bottom(底部)和 top(顶部)面使用 oak_log_top.png,侧面则使用 oak_log.png。
WARNING
TextureMap 中的所有 TextureKey 必须与父方块模型中的所有 TextureKey 匹配!
BlockStateSupplier 方法 BlockStateSupplier 包含所有方块状态变体、旋转以及其他选项(如 uvlock)。
private static BlockStateSupplier createVerticalSlabBlockStates(Block vertSlabBlock, Identifier vertSlabId, Identifier fullBlockId) {
VariantSetting<Boolean> uvlock = VariantSettings.UVLOCK;
VariantSetting<VariantSettings.Rotation> yRot = VariantSettings.Y;
return VariantsBlockStateSupplier.create(vertSlabBlock).coordinate(BlockStateVariantMap.create(VerticalSlabBlock.FACING, VerticalSlabBlock.SINGLE)
.register(Direction.NORTH, true, BlockStateVariant.create().put(VariantSettings.MODEL, vertSlabId).put(uvlock, true))
.register(Direction.EAST, true, BlockStateVariant.create().put(VariantSettings.MODEL, vertSlabId).put(uvlock, true).put(yRot, VariantSettings.Rotation.R90))
.register(Direction.SOUTH, true, BlockStateVariant.create().put(VariantSettings.MODEL, vertSlabId).put(uvlock, true).put(yRot, VariantSettings.Rotation.R180))
.register(Direction.WEST, true, BlockStateVariant.create().put(VariantSettings.MODEL, vertSlabId).put(uvlock, true).put(yRot, VariantSettings.Rotation.R270))
.register(Direction.NORTH, false, BlockStateVariant.create().put(VariantSettings.MODEL, fullBlockId).put(uvlock, true))
.register(Direction.EAST, false, BlockStateVariant.create().put(VariantSettings.MODEL, fullBlockId).put(uvlock, true))
.register(Direction.SOUTH, false, BlockStateVariant.create().put(VariantSettings.MODEL, fullBlockId).put(uvlock, true))
.register(Direction.WEST, false, BlockStateVariant.create().put(VariantSettings.MODEL, fullBlockId).put(uvlock, true)));
}首先,我们使用 VariantsBlockStateSupplier.create() 创建一个新的 VariantsBlockStateSupplier。 然后我们创建一个新的 BlockStateVariantMap,它包含方块的所有变体的参数,在本例中是 FACING 和 SINGLE,并将其传递给 VariantsBlockStateSupplier。 指定使用 .register() 时使用哪个模型和哪些变换(uvlock、rotation)。 例如:
最后一步——创建一个可以调用的实际方法并生成 JSON。 但这些参数是用来做什么的呢?
BlockStateModelGenerator generator,与传递到 generateBlockStateModels 的生成器相同。Block vertSlabBlock 是我们将生成 JSON 的方块。Block fullBlock 是当 SINGLE 属性为 false 时使用的模型 = 台阶方块看起来像一个完整方块。TextureMap textures 定义了模型使用的实际纹理。 参见使用纹理映射章节。public static void registerVerticalSlab(BlockStateModelGenerator generator, Block vertSlabBlock, Block fullBlock, TextureMap textures) {
Identifier slabModel = VERTICAL_SLAB.upload(vertSlabBlock, textures, generator.modelCollector);
Identifier fullBlockModel = ModelIds.getBlockModelId(fullBlock);
generator.blockStateCollector.accept(createVerticalSlabBlockStates(vertSlabBlock, slabModel, fullBlockModel));
generator.registerParentedItemModel(vertSlabBlock, slabModel);
}首先,我们使用 VERTICAL_SLAB.upload() 获取单个台阶模型的 Identifier。 然后我们使用 ModelIds.getBlockModelId() 获取完整方块模型的 Identifier,并将这两个模型传递给 createVerticalSlabBlockStates。 BlockStateSupplier 被传递到 blockStateCollector,从而实际生成 JSON 文件。 另外,我们使用 BlockStateModelGenerator.registerParentedItemModel() 为垂直台阶物品创建一个模型。
就这样! 现在剩下要做的就是在 ModelProvider 中调用方法:
CustomBlockStateModelGenerator.registerVerticalSlab(
blockStateModelGenerator,
ModBlocks.VERTICAL_OAK_LOG_SLAB,
Blocks.OAK_LOG,
CustomBlockStateModelGenerator.blockAndTopForEnds(Blocks.OAK_LOG)
);您可以查看 Fabric API 中的示例测试和此文档的 参考模组 以获取更多信息。
您还可以通过浏览模组的开源代码找到更多使用自定义数据生成方法的示例,例如 Fellteros 的 Vanilla+ Blocks 和 Vanilla+ Verticals。