User Tools

Site Tools


zh_cn:tutorial:custom_model

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
zh_cn:tutorial:custom_model [2022/03/21 04:12] – [注册模型] solidblockzh_cn:tutorial:custom_model [2024/08/27 04:34] (current) – 更新翻译 solidblock
Line 1: Line 1:
 ====== 使用自定义模型动态渲染方块和物品 ====== ====== 使用自定义模型动态渲染方块和物品 ======
-可以通过方块模型 JSON 文件将模型添加到游戏,但也可以通过 Java 代码来渲染。本教程中,我们将会一个四面熔炉模型添加到游戏。+可以通过方块模型 JSON 文件将模型添加到游戏,但也可以通过 Java 代码来渲染。本教程中,我们将会一个四面熔炉模型添加到游戏。
  
 注意模型会在区块被重建时渲染。如果需要更加动态的渲染,可以使用 ''BlockEntityRenderer'':[[zh_cn:tutorial:blockentityrenderers|方块实体渲染器]]。 注意模型会在区块被重建时渲染。如果需要更加动态的渲染,可以使用 ''BlockEntityRenderer'':[[zh_cn:tutorial:blockentityrenderers|方块实体渲染器]]。
Line 8: Line 8:
  
 <code java> <code java>
 +@Environment(EnvType.CLIENT)
 public class FourSidedFurnaceModel implements UnbakedModel, BakedModel, FabricBakedModel { public class FourSidedFurnaceModel implements UnbakedModel, BakedModel, FabricBakedModel {
 </code> </code>
  
 ==== Sprites ==== ==== Sprites ====
-渲染纹理离不开''Sprite''。我们必须先创建一个''SpriteIdentifier''然后在bake模型时得到对应的''Sprite'' +渲染纹理离不开''Sprite''。我们必须先创建一个''SpriteIdentifier''然后在bake模型时得到对应的''Sprite''。这里,我们会使用两个熔炉纹理,都是方块纹理,所以要从方块atlas''PlayerScreenHandler.BLOCK_ATLAS_TEXTURE''中加载。
-这里,我们会使用两个熔炉纹理。它们是方块纹理,所以要从方块atlas''SpriteAtlasTexture.BLOCK_ATLAS_TEX''中加载。+
 <code java> <code java>
 +    // 对于 1.21 之前的版本,将 `Identifier.ofVanilla` 替换为 `new Identifier`。
     private static final SpriteIdentifier[] SPRITE_IDS = new SpriteIdentifier[]{     private static final SpriteIdentifier[] SPRITE_IDS = new SpriteIdentifier[]{
-            new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("minecraft:block/furnace_front_on")), +            new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, Identifier.ofVanilla("block/furnace_front_on")), 
-            new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("minecraft:block/furnace_top"))+            new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, Identifier.ofVanilla("block/furnace_top"))
     };     };
-    private Sprite[] SPRITES = new Sprite[2];+    private Sprite[] SPRITES = new Sprite[SPRITE_IDS.length]
 + 
 +    // 一些常量,以避免魔法数据,需要匹配 SPRITE_IDS 
 +    private static final int SPRITE_SIDE = 0; 
 +    private static final int SPRITE_TOP = 1;
 </code> </code>
  
-==== Meshes ====+==== Mesh ====
 ''Mesh'' 是准备通过 Fabric Rendering API 渲染的游戏形状。我们会添加一个 Mesh 到我们的类中,然后在 bake 模型时将其构造(build)。 ''Mesh'' 是准备通过 Fabric Rendering API 渲染的游戏形状。我们会添加一个 Mesh 到我们的类中,然后在 bake 模型时将其构造(build)。
 <code java> <code java>
Line 28: Line 33:
 </code> </code>
  
-==== UnbakedModel方法 ====+==== UnbakedModel 方法 ====
 <code java> <code java>
     @Override     @Override
Line 36: Line 41:
  
     @Override     @Override
-    public Collection<SpriteIdentifier> getTextureDependencies(Function<Identifier, UnbakedModel> unbakedModelGetter, Set<Pair<String, String>> unresolvedTextureReferences) { +    public void setParents(Function<Identifier, UnbakedModel> modelLoader) { 
-        return Arrays.asList(SPRITE_IDS); // 模型(以及其模型依赖,依赖的依赖等)依赖的纹理。+        // 模型继承有关我们这里还不需要使用到
     }     }
  
  
     @Override     @Override
-    public BakedModel bake(ModelLoader loader, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) { +    public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer) { 
-        // 获得sprites+        // 获得 sprites
         for(int i = 0; i < 2; ++i) {         for(int i = 0; i < 2; ++i) {
             SPRITES[i] = textureGetter.apply(SPRITE_IDS[i]);             SPRITES[i] = textureGetter.apply(SPRITE_IDS[i]);
         }         }
-        // 用Renderer API构建mesh+        // 用 Renderer API 构建 mesh
         Renderer renderer = RendererAccess.INSTANCE.getRenderer();         Renderer renderer = RendererAccess.INSTANCE.getRenderer();
         MeshBuilder builder = renderer.meshBuilder();         MeshBuilder builder = renderer.meshBuilder();
Line 53: Line 58:
  
         for(Direction direction : Direction.values()) {         for(Direction direction : Direction.values()) {
-            int spriteIdx = direction == Direction.UP || direction == Direction.DOWN ? 0+            int spriteIdx = direction == Direction.UP || direction == Direction.DOWN ? SPRITE_TOP SPRITE_SIDE
-            // 将新的面(face)添加到mesh+            // 将新的面(face)添加到 mesh
             emitter.square(direction, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);             emitter.square(direction, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);
-            // 设置面的sprite,必须在.square()之后调用 +            // 设置面的 sprite,必须在 .square() 之后调用 
-            // 我们还没有指定任何uv坐标,所以我们使用整个纹理,BAKE_LOCK_UV恰好就这么做。+            // 我们还没有指定任何 uv 坐标,所以我们使用整个纹理,BAKE_LOCK_UV 恰好就这么做。
             emitter.spriteBake(0, SPRITES[spriteIdx], MutableQuadView.BAKE_LOCK_UV);             emitter.spriteBake(0, SPRITES[spriteIdx], MutableQuadView.BAKE_LOCK_UV);
             // 启用纹理使用             // 启用纹理使用
             emitter.spriteColor(0, -1, -1, -1, -1);             emitter.spriteColor(0, -1, -1, -1, -1);
-            // 将quad添加到mesh+            // 将 quad 添加到 mesh
             emitter.emit();             emitter.emit();
         }         }
Line 75: Line 80:
     @Override     @Override
     public List<BakedQuad> getQuads(BlockState state, Direction face, Random random) {     public List<BakedQuad> getQuads(BlockState state, Direction face, Random random) {
-        return null; // 不需要,因为我们使用的是FabricBakedModel+        return Collections.emptyList(); // 不需要,因为我们使用的是 FabricBakedModel,但是最好不要返回 null,因为有些模组会要调用这个函数
     }     }
  
Line 100: Line 105:
     @Override     @Override
     public Sprite getParticleSprite() {     public Sprite getParticleSprite() {
-        return SPRITES[1]; // 方块被破坏时产生的颗粒,使用furnace_top+        return SPRITES[1]; // 方块被破坏时产生的颗粒,使用 furnace_top
     }     }
  
Line 135: Line 140:
 } }
 </code> </code>
 +
 +注意:确保覆盖了 ''FabricBakedModel'' 方法,接口有 ''default'' 的实现!
  
 ===== 注册模型 ===== ===== 注册模型 =====
-我们先写一个 ''ModelResourceProvider'',这个接口允许你在游戏尝试从 JSON 加载时提供一个''UnbakedModel''。参看[[https://github.com/FabricMC/fabric/blob/1.16/fabric-models-v0/src/main/java/net/fabricmc/fabric/api/client/model/ModelResourceProvider.java|本文档]]以了解详情。需要注意的是,对于每个模型,''loadModelResource()'' 都会调用一次。 
  
-我们用 ''tutorial:block/four_sided_furnace'' 这个名称注册模型。+要让模型在游戏内被渲染需要注册,为注册我们需要创建 ''ModelLoadingPlugin'': 
 <code java> <code java>
 @Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
-public class TutorialModelProvider implements ModelResourceProvider +public class TutorialModelLoadingPlugin implements ModelLoadingPlugin 
-    public static final Identifier FOUR_SIDED_FURNACE_MODEL = new Identifier("tutorial:block/four_sided_furnace");+    public static final ModelIdentifier FOUR_SIDED_FURNACE_MODEL = new ModelIdentifier(Identifier.of("tutorial", "four_sided_furnace"), ""); 
     @Override     @Override
-    public UnbakedModel loadModelResource(Identifier identifier, ModelProviderContext modelProviderContextthrows ModelProviderException +    public void onInitializeModelLoader(Context pluginContext) { 
-        if(identifier.equals(FOUR_SIDED_FURNACE_MODEL)) { +        // 我们需要在模型被加载时添加模型 
-            return new FourSidedFurnaceModel(); +        pluginContext.modifyModelOnLoad().register((original, context) -> { 
-        } else { +            // 这个每次加载模型时都会调用,所以确保我们只针对我们的 
-            return null+            final ModelIdentifier id = context.topLevelId(); 
-        }+            if(id != null && id.equals(FOUR_SIDED_FURNACE_MODEL)) { 
 +                return new FourSidedFurnaceModel(); 
 +            } else { 
 +                // 如果不修改模型,就照样返回原来的 
 +                return original; 
 +            } 
 +        });
     }     }
 } }
Line 161: Line 175:
     @Override     @Override
     public void onInitializeClient() {     public void onInitializeClient() {
-        ModelLoadingRegistry.INSTANCE.registerResourceProvider(rm -> new TutorialModelProvider());+        ModelLoadingPlugin.register(new TutorialModelLoadingPlugin());
                  
-        /* 其他客户端定的初始化 */+        /* 其他客户端定的初始化 */
     }     }
 } }
Line 169: Line 183:
  
 不要忘记在 ''fabric.mod.json'' 中注册这个入口点,如果还没有完成的话: 不要忘记在 ''fabric.mod.json'' 中注册这个入口点,如果还没有完成的话:
-<code json+<code javascript
-/* ... */+
 +  [...]
   "entrypoints": {   "entrypoints": {
-    /* ... */+    [...]
     "client": [     "client": [
       "net.fabricmc.example.ExampleModClient"       "net.fabricmc.example.ExampleModClient"
     ]     ]
   },   },
 +  [...]
 +}
 </code> </code>
  
 ===== 使用模型 ===== ===== 使用模型 =====
-现在可以注册你的方块以使用新模型。比如,如果你的方块只有一个状态,把这个放在''assets/模组id/blockstates/你的方块id.json''之下。 +你可以[[blocks|注册方块]]以使用你的新模型。我们假设方块的 id 是 ''tutorial:four_sided_furnace'' 
-<code json>+ 
 +<code java TutorialBlocks.java> 
 +public final class TutorialBlocks { 
 +  [...] 
 +  public static final Block FOUR_SIDED_FURNACE = register("four_sided_furnace", new Block(AbstractBlock.Settings.copy(Blocks.FURNACE).luminance(x -> 15))); 
 +  [...] 
 +
 +</code> 
 +<code javascript src/main/resources/assets/tutorial/blockstates/four_sided_furnace.json>
 { {
   "variants": {   "variants": {
Line 198: Line 223:
 ==== 更新模型 ==== ==== 更新模型 ====
 我们复用相同的模型类,但是有一点点小改变: 我们复用相同的模型类,但是有一点点小改变:
-  * 我们会使用 ''ModelTransformation'',根据其位置(在右手、在左手、在 GUI 中、在物品展示框中,等等)将其旋转/平移/缩放。就像为平常的方块创建模型一样,我们使用在 bake 模型时加载的“minecraft:block/block”格式+  * 我们会使用 ''ModelTransformation'',根据其位置(在右手、在左手、在 GUI 中、在物品展示框中,等等)将其旋转/平移/缩放。就像为一般的方块创建模型一样,我们可以使用由 Fabric 在 ''ModelHelper.MODEL_TRANSFORM_BLOCK'' 提供变换
  
 现在对 ''FourSidedFurnaceModel'' 类进行如下修改: 现在对 ''FourSidedFurnaceModel'' 类进行如下修改:
 <code java> <code java>
-    // 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()     // 我们需要实现 getTransformation() 和 getOverrides()
     @Override     @Override
     public ModelTransformation getTransformation() {     public ModelTransformation getTransformation() {
-        return transformation;+        return ModelHelper.MODEL_TRANSFORM_BLOCK;
     }     }
  
Line 243: Line 248:
     @Override     @Override
     public void emitItemQuads(ItemStack itemStack, Supplier<Random> supplier, RenderContext renderContext) {     public void emitItemQuads(ItemStack itemStack, Supplier<Random> supplier, RenderContext renderContext) {
-        renderContext.meshConsumer().accept(mesh);+        mesh.outputTo(context.getEmitter());
     }     }
 </code> </code>
  
 ==== 加载模型 ==== ==== 加载模型 ====
-更新我们先前创建的 ''ModelResourceProvider''+更新我们先前创建的 ''TutorialModelLoadingPlugin''
 <code java> <code java>
-public class TutorialModelProvider implements ModelResourceProvider +@Environment(EnvType.CLIENT) 
-    public static final FourSidedFurnaceModel FOUR_SIDED_FURNACE_MODEL = new FourSidedFurnaceModel(); +public class TutorialModelLoadingPlugin implements ModelLoadingPlugin 
-    public static final Identifier FOUR_SIDED_FURNACE_MODEL_BLOCK = new Identifier("tutorial:block/four_sided_furnace"); +    public static final ModelIdentifier FOUR_SIDED_FURNACE_MODEL = new ModelIdentifier(Identifier.of("tutorial", "four_sided_furnace"), ""); 
-    public static final Identifier FOUR_SIDED_FURNACE_MODEL_ITEM = new Identifier("tutorial:item/four_sided_furnace");+    public static final ModelIdentifier FOUR_SIDED_FURNACE_MODEL_ITEM = new ModelIdentifier(Identifier.of("tutorial", "four_sided_furnace"), "inventory");
  
     @Override     @Override
-    public UnbakedModel loadModelResource(Identifier identifier, ModelProviderContext modelProviderContextthrows ModelProviderException +    public void onInitializeModelLoader(Context pluginContext) { 
-        if(identifier.equals(FOUR_SIDED_FURNACE_MODEL_BLOCK) || identifier.equals(FOUR_SIDED_FURNACE_MODEL_ITEM)) { +        // 我们需要在模型被加载时添加模型 
-            return FOUR_SIDED_FURNACE_MODEL+        pluginContext.modifyModelOnLoad().register((original, context) -> { 
-        } else { +            // 这个每次加载模型时都会调用,所以确保我们只针对我们的 
-            return null+            final ModelIdentifier id = context.topLevelId(); 
-        }+            if (id != null && (id.equals(FOUR_SIDED_FURNACE_MODEL) || id.equals(FOUR_SIDED_FURNACE_MODEL_ITEM))) { 
 +                return new FourSidedFurnaceModel()
 +            } else { 
 +                // 如果不修改模型,就照样返回原来的 
 +                return original; 
 +            } 
 +        });
     }     }
 } }
Line 272: Line 283:
  
 ===== 更加动态的渲染 ===== ===== 更加动态的渲染 =====
-''emitBlockQuads'' 和 ''emitItemQuads'' 中的 ''renderContext'' 参数包含一个你可以用于在飞行中(on the fly)建立模型的 ''QuadEmitter''+''emitBlockQuads'' 和 ''emitItemQuads'' 中的 ''renderContext'' 参数包含一个你可以用于实时建立模型的 ''QuadEmitter''
 <code java> <code java>
     @Override     @Override
zh_cn/tutorial/custom_model.1647835953.txt.gz · Last modified: 2022/03/21 04:12 by solidblock