This is an old revision of the document!
颜色提供器
有没有想过,草和树叶如何根据生物群系而改变色调的?皮革盔甲如何具有看似无限的颜色模式?答案是颜色提供器,这个可以允许你根据位置、NBT、方块状态等属性为方块或物品的模型纹理设置色调或者着色。
原版例子
首先,现有的哪些原版内容使用颜色提供器?一些示例包括:
- 草
- 树叶
- 皮革盔甲着色
- 红石线
- 西瓜、甘蔗和睡莲等植物
- 药箭
颜色提供器功能强大,但是 Mojang 选择对混凝土、羊毛和玻璃等有色方块坚持使用单独的纹理。此时的主要用例是针对受生物群系的方块,以及对现有纹理的细微调整,例如药箭的彩色末端。
颜色提供器背后的概念很简单。你为之注册方块和物品,并在渲染该方块或物品的模型时,颜色提供器对纹理的每一层应用色调调整。两个提供器都可以访问模型的层,这意味着您可以分别对模型的每个部分进行色调设置,皮革盔甲和药箭就是这种情况。当您只想更改几个像素而不是整个纹理时,这很有用。
请记住,颜色提供器是客户端机制。确保将与其相关的所有代码放入客户端初始化器中。
方块颜色提供器
要将方块注册到方块颜色提供器,您需要使用 Fabric 的 ColorProviderRegistry
。此类中有一个 BLOCK
和 ITEM
提供器的实例,可以其调用 register
。register
方法接受颜色提供器的一个实例,以及想要使用该提供器进行着色的每个方块的变量。
首先,先在 TutoriaoBlocks
类创建方块,关于如何创建方块,见 blocks:
- TutorialBlocks.class
public final class TutorialBlocks { [...] public static final Block COLOR_BLOCK = register("color_block", new Block(AbstractBlock.Settings.create())); }
简单添加个方块状态文件:
Then add a simple block states file:
- src/main/resources/assets/tutorial/blockstates/color_block.json
{ "variants": { "": { "model": "tutorial:block/color_block" } } }
在你的 ClientModInitializer
:
- ExampleModClient.java
@Environment(EnvType.CLIENT) public class ExampleModClient implements ClientModInitializer { @Override public void onInitializeClient() { // ... ColorProviderRegistry.BLOCK.register((state, view, pos, tintIndex) -> 0x3495eb, TutorialBlocks.COLOR_BLOCK); } }
如果还没有的话,记得在你的 fabric.mod.json 中注册:
{ // ... "entrypoints": { // ... "client": [ "net.fabricmc.example.ExampleModClient" ] }, // ... }
然后创建带有 tintindex 的模型:模型也很重要,这里需要注意的是,你一定要为模型的每一个你需要着色的部分定义tintindex。如要查看这个的例子,请参考 leaves.json
,这是原版树叶使用的基本模型。这里是我们方块使用的模型:
【【
我们在这里所做的只是说:“Hi,MY_BLOCK
应被着色为 0x3495eb”,也就是蓝色。你有 BlockState、World 和 BlockPos 的环境,基于这些环境的生物群系或者位置等来改变颜色。最终的 int 是 tintIndex,每个都会单独为颜色获取一个,但是在此例中,我们只返回蓝色。
如果你需要在颜色提供器中访问 BlockEntity
数据,你需要实现 RenderAttachmentBlockEntity
以返回你需要的数据。这是因为方块可以在单独的线程渲染,所以直接访问数据并不安全。而且,如果使用 getBlockState
查询方块,你无法查看整个世界——确保你只查询当前位置的 ±2 方块范围内的位置。
】】
- src/main/resources/assets/tutorial/models/block/color_block.json
{ "parent": "block/block", "textures": { "all": "block/white_concrete", "particle": "#all" }, "elements": [ { "from": [ 0, 0, 0 ], "to": [ 16, 16, 16 ], "faces": { "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "tintindex": 0, "cullface": "down" }, "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "tintindex": 0, "cullface": "up" }, "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "tintindex": 0, "cullface": "north" }, "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "tintindex": 0, "cullface": "south" }, "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "tintindex": 0, "cullface": "west" }, "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "tintindex": 0, "cullface": "east" } } } ] }
在这个实例里面,我们添加了单一的 tintindex,出现在 tintIndex
参数中(着色索引 0)。事实上,特准可以直接继承 minecraft:block/leaves
模型,因为也使用的带有 tintindex 的方块。所以可以将上面的模型替换为:
- src/main/resources/assets/tutorial/models/block/color_block.json
{ "parent": "block/leaves", "textures": { "all": "block/white_concrete" } }
注意:方块颜色是有缓存的,如果提供了随时间改变的颜色,这个改变不会直接生效,除非附近有方块更新。
这是最终结果——请注意,原始模型使用了 white_concrete
(白色混凝土)纹理(下图使用了 imgur 图床):
带有颜色提供器的方块实体
If you need to access BlockEntity
data in the color provider, you'll want to override getRenderData()
method from the RenderDataBlockEntity
, which is an interface of Fabric API but injected to BlockEntity
. If you're using old versions, try implementing RenderAttachmentBlockEntity
and returning the data you need.
This is because blocks can be rendered on separate threads, so accessing the data directly is not safe. Additionally, if you query blocks with getBlockState
you won't be able to view the entire world - make sure you only query within ±2 blocks x/y/z of the current position.
In this case, we create a ColorBlock
class and a ColorBlockEntity
class, and connect the block with block entity (more information see blockentity).
- ColorBlock.java
public class ColorBlock extends BlockWithEntity { public ColorBlock(Settings settings) { super(settings); } @Override protected MapCodec<? extends ColorBlock> getCodec() { return createCodec(ColorBlock::new); } @Nullable @Override public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { return new ColorBlockEntity(pos, state); } @Override protected BlockRenderType getRenderType(BlockState state) { return BlockRenderType.MODEL; } }
- ColorBlockEntity.java
public class ColorBlockEntity extends BlockEntity { public int color = 0x3495eb; public ColorBlockEntity(BlockPos pos, BlockState state) { super(TutorialBlockEntityTypes.COLOR_BLOCK, pos, state); } // The following two methods specify serialization of color data. @Override protected void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) { super.readNbt(nbt, registryLookup); color = nbt.getInt("color"); // When the data is modified through "/data" command, // or placed by an item with "block_entity_data" component, // the render color will be updated. if (world != null) { world.updateListeners(pos, getCachedState(), getCachedState(), 0); } } @Override protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) { super.writeNbt(nbt, registryLookup); nbt.putInt("color", color); } @Nullable @Override public Packet<ClientPlayPacketListener> toUpdatePacket() { return BlockEntityUpdateS2CPacket.create(this); } @Override public NbtCompound toInitialChunkDataNbt(RegistryWrapper.WrapperLookup registryLookup) { return createNbt(registryLookup); } @Override public @Nullable Object getRenderData() { // this is the method from `RenderDataBlockEntity` class. return color; } }
In the TutorialBlocks
class, replace new Block
with new ColorBlock
:
public static final ColorBlock COLOR_BLOCK = register("color_block", new ColorBlock(AbstractBlock.Settings.create()));
In the TutorialBlockEntityTypes
class:
public static final BlockEntityType<ColorBlockEntity> COLOR_BLOCK = register("color_block", BlockEntityType.Builder.create(ColorBlockEntity::new, TutorialBlocks.COLOR_BLOCK).build());
Now we modify onUseWithItem
method so that the color changes when you interact the block with a dye:
- ColorBlock.java
public class ColorBlock extends BlockWithEntity { [...] @Override protected ItemActionResult onUseWithItem(ItemStack stack, BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { if (stack.getItem() instanceof DyeItem dyeItem) { if (world.getBlockEntity(pos) instanceof ColorBlockEntity colorBlockEntity) { final int newColor = dyeItem.getColor().getEntityColor(); final int originalColor = colorBlockEntity.color; colorBlockEntity.color = ColorHelper.Argb.averageArgb(newColor, originalColor); stack.decrementUnlessCreative(1, player); colorBlockEntity.markDirty(); world.updateListeners(pos, state, state, 0); } } return super.onUseWithItem(stack, state, world, pos, player, hand, hit); } [...] }
Finally, modify the color provider to use the render data. We call FabricBlockView.getBlockEntityRenderData
to ensure thread-safety and data-consistency.
- ExampleModClient.java
@Environment(EnvType.CLIENT) public class ExampleModClient implements ClientModInitializer { [...] @Override public void onInitializeClient() { [...] ColorProviderRegistry.BLOCK.register((state, view, pos, tintIndex) -> view != null && view.getBlockEntityRenderData(pos) instanceof Integer integer ? integer : 0x3495eb, TutorialBlocks.COLOR_BLOCK); } }
Now done! Then you can check whether the following work correctly:
- When you interact the block with a dye, the color should change.
- When you modify the color through
/data
command, the color should change. - When you pick (press mouse wheel) the block with
Ctrl
pressed, and place the block, it should display as the expected color. - When you leave the world and re-enter, the color should be kept.
物品颜色提供器
物品是类似的,区别在于提供的上下文。不访问状态、世界和位置,而是访问 ItemStack
。
物品模型可以直接继承使用 tintindex 的方块模型:
- ExampleModClient.java
@Environment(EnvType.CLIENT) public class ExampleModClient implements ClientModInitializer { @Override public void onInitializeClient() { // ... ColorProviderRegistry.ITEM.register((stack, tintIndex) -> 0x3495eb, TutorialBlocks.COLOR_BLOCK); } }
这会以像方块那样的方法为你物品栏中的物品提供色相。
限制
使用颜色提供器的一个关键问题是物品的提供器中缺少上下文。这就是为什么原版草不会根据您站立的位置改变物品栏中的颜色的原因。为了实现诸如方块的颜色变体(混凝土、玻璃、羊毛等)之类的东西,建议您为每个版本简单地提供单独的纹理。