This is an old revision of the document!
Table of Contents
使用自定义模型动态渲染方块和物品
可以通过方块模型JSON文件将模型添加到游戏,但也可以通过Java代码来渲染。本教程中,我们将会将一个四面熔炉模型添加到游戏。
注意模型会在区块被重建时渲染。如果需要更加动态的渲染,可以使用BlockEntityRenderer
: 方块实体渲染器。
创建模型
模型第一次在Minecraft被注册时,其原始数据包含在UnbakedModel
中。比如,这个数据可能会包括形状或材质名。然后在初始化过程中,UnbakedModel::bake()
创建一个BakedModel
以用于渲染。为了使得渲染尽可能快,这个baking的过程中会尽可能多地操作。我们也会实现FabricBakedModel
以利用Fabric Renderer API。现在创建一个实现所有三个接口的FourSidedFurnace
模型。
public class FourSidedFurnaceModel implements UnbakedModel, BakedModel, FabricBakedModel {
Sprites
渲染材质离不开Sprite
。我们必须先创建一个SpriteIdentifier
然后在bake模型时得到对应的Sprite
。
这里,我们会使用两个熔炉材质。它们是方块材质,所以要从方块atlasSpriteAtlasTexture.BLOCK_ATLAS_TEX
中加载。
private static final SpriteIdentifier[] SPRITE_IDS = new SpriteIdentifier[]{ new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("minecraft:block/furnace_front_on")), new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("minecraft:block/furnace_top")) }; private Sprite[] SPRITES = new Sprite[2];
Meshes
Mesh
是预备通过Fabric Rendering API渲染的游戏形状。我们会添加一个到我们的类中,然后在bake模型时加载它。
private Mesh mesh;
UnbakedModel方法
@Override public Collection<Identifier> getModelDependencies() { return Collections.emptyList(); // 模型不依赖于其他模型。 } @Override public Collection<SpriteIdentifier> getTextureDependencies(Function<Identifier, UnbakedModel> unbakedModelGetter, Set<Pair<String, String>> unresolvedTextureReferences) { return Arrays.asList(SPRITE_IDS); // 本模型(以及其模型依赖,依赖的依赖,等)依赖的材质。 } @Override public BakedModel bake(ModelLoader loader, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) { // 获得sprites for(int i = 0; i < 2; ++i) { SPRITES[i] = textureGetter.apply(SPRITE_IDS[i]); } // 用Renderer API构建mesh Renderer renderer = RendererAccess.INSTANCE.getRenderer(); MeshBuilder builder = renderer.meshBuilder(); QuadEmitter emitter = builder.getEmitter(); for(Direction direction : Direction.values()) { int spriteIdx = direction == Direction.UP || direction == Direction.DOWN ? 1 : 0; // 将新的面(face)添加到mesh emitter.square(direction, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f); // 设置面的sprite,必须在.square()之后调用 // 我们还没有指定任何uv坐标,所以我们使用整个材质,BAKE_LOCK_UV恰好就这么做。 emitter.spriteBake(0, SPRITES[spriteIdx], MutableQuadView.BAKE_LOCK_UV); // 启用材质使用 emitter.spriteColor(0, -1, -1, -1, -1); // 将quad添加到mesh emitter.emit(); } mesh = builder.build(); return this; }
BakedModel方法
注意这里不是所有的方法都会被Fabric Renderer使用,所以我们并不关心这个实现。
@Override public List<BakedQuad> getQuads(BlockState state, Direction face, Random random) { return null; // 不需要,因为我们使用的是FabricBakedModel } @Override public boolean useAmbientOcclusion() { return true; // 环境光遮蔽:我们希望方块在有临近方块时显示阴影 } @Override public boolean isBuiltin() { return false; } @Override public boolean hasDepth() { return false; } @Override public boolean isSideLit() { return false; } @Override public Sprite getSprite() { return SPRITES[1]; // 方块破坏材质,使用furnace_top } @Override public ModelTransformation getTransformation() { return null; } @Override public ModelOverrideList getOverrides() { return null; }
FabricBakedModel方法
@Override public boolean isVanillaAdapter() { return false; // false以触发FabricBakedModel渲染 } @Override public void emitBlockQuads(BlockRenderView blockRenderView, BlockState blockState, BlockPos blockPos, Supplier<Random> supplier, RenderContext renderContext) { // 渲染函数 // 我们仅渲染mesh renderContext.meshConsumer().accept(mesh); } @Override public void emitItemQuads(ItemStack itemStack, Supplier<Random> supplier, RenderContext renderContext) { } }
注册模型
我们先写一个ModelResourceProvider
,允许你在游戏尝试从JSON加载时提供一个UnbakedModel
的界面。参看本文档以了解详情。重要的部分是,对于每个模型都会调用一次loadModelResource()
。
我们在名称tutorial:block/four_sided_furnace
之下注册模型。
public class TutorialModelProvider implements ModelResourceProvider { public static final Identifier FOUR_SIDED_FURNACE_MODEL = new Identifier("tutorial:block/four_sided_furnace"); @Override public UnbakedModel loadModelResource(Identifier identifier, ModelProviderContext modelProviderContext) throws ModelProviderException { if(identifier.equals(FOUR_SIDED_FURNACE_MODEL)) { return new FourSidedFurnaceModel(); } else { return null; } } }
现在我们要将这个类注册到客户端初始化器中,the entry point for client-specific code.
public class ExampleModClient implements ClientModInitializer { @Override public void onInitializeClient() { ModelLoadingRegistry.INSTANCE.registerResourceProvider(rm -> new TutorialModelProvider()); /* 其他客户端指定的初始化 */ } }
不要忘记在fabric.mod.json
中注册这个entrypoint,如果还没有完成的话:
/* ... */ "entrypoints": { /* ... */ "client": [ "net.fabricmc.example.ExampleModClient" ] },
使用模型
你现在可以注册你的方块以使用新模型。比如,如果你的方块只有一个状态,把这个放在assets/你的模组id/blockstates/你的方块id.json
之下。
{ "variants": { "": { "model": "tutorial:block/four_sided_furnace" } } }
当然,你可以实现更加复杂的渲染。玩得开心!
渲染物品
正如上图中看到的,物品渲染不正确。我们来将其修复。
更新模型
我们复用相同的模型类,但是有一点点小改变:
- 我们会使用
ModelTransformation
,根据其位置(在右手、在左手、在GUI中、在物品展示框中,等等)将其旋转/平移/缩放。就像为平常的方块创建模型一样,我们会使用会在bake模型时加载的“minecraft:block/block”的格式。
我们会更新我们的 FourSidedFurnaceModel
类,如下:
// Minecraft默认方块模型 private static final Identifier DEFAULT_BLOCK_MODEL = new Identifier("minecraft:block/block"); private ModelTransformation transformation; // 我们会将默认模型添加到其依赖中 public Collection<Identifier> getModelDependencies() { return Arrays.asList(DEFAULT_BLOCK_MODEL); } // 我们会给bake函数添加一点逻辑 @Override public BakedModel bake(ModelLoader loader, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) { // 加载默认方块模型 JsonUnbakedModel defaultBlockModel = (JsonUnbakedModel) loader.getOrLoadModel(DEFAULT_BLOCK_MODEL); // 获取ModelTransformation transformation = defaultBlockModel.getTransformations(); /* 先前的代码 */ } // 我们会实现 getTransformation() 和 getOverrides() @Override public ModelTransformation getTransformation() { return transformation; } @Override public ModelOverrideList getOverrides() { return ModelOverrideList.EMPTY; } // 我们也会使用此方法以使得物品渲染时有正确的光照。尝试将其设为false,你就会看到不同。 @Override public boolean isSideLit() { return true; } // 最终,我们实现物品渲染函数 @Override public void emitItemQuads(ItemStack itemStack, Supplier<Random> supplier, RenderContext renderContext) { renderContext.meshConsumer().accept(mesh); }
加载模型
更新我们先前创建的 ModelResourceProvider
:
public class TutorialModelProvider implements ModelResourceProvider { public static final FourSidedFurnaceModel FOUR_SIDED_FURNACE_MODEL = new FourSidedFurnaceModel(); public static final Identifier FOUR_SIDED_FURNACE_MODEL_BLOCK = new Identifier("tutorial:block/four_sided_furnace"); public static final Identifier FOUR_SIDED_FURNACE_MODEL_ITEM = new Identifier("tutorial:item/four_sided_furnace"); @Override public UnbakedModel loadModelResource(Identifier identifier, ModelProviderContext modelProviderContext) throws ModelProviderException { if(identifier.equals(FOUR_SIDED_FURNACE_MODEL_BLOCK) || identifier.equals(FOUR_SIDED_FURNACE_MODEL_ITEM)) { return FOUR_SIDED_FURNACE_MODEL; } else { return null; } } }
最终结果
Et voilà! 享受吧!
更加动态的渲染
The renderContext
parameter in emitBlockQuads
and emitItemQuads
contains a QuadEmitter
which you can use to build a model on the fly.
@Override public void emitBlockQuads(BlockRenderView blockRenderView, BlockState blockState, BlockPos blockPos, Supplier<Random> supplier, RenderContext renderContext) { QuadEmitter emitter = renderContext.getEmitter(); /* With this emitter, you can directly append the quads to the chunk model. */ }