User Tools

Site Tools


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
tutorial:custom_model [2023/06/25 21:41] – fix typo (EntType -> EnvType) andrew6ranttutorial:custom_model [2024/08/27 04:33] (current) solidblock
Line 15: Line 15:
  
 ==== Sprites ==== ==== Sprites ====
-A ''Sprite'' is necessary for rendering a texture. We must first create a ''SpriteIdentifier'' and then get the corresponding ''Sprite'' while baking the model. +A ''Sprite'' is necessary for rendering a texture. We must first create a ''SpriteIdentifier'' and then get the corresponding ''Sprite'' while baking the model. Here, we will use two furnace textures. They are block textures, so they must be loaded from the block atlas ''PlayerScreenHandler.BLOCK_ATLAS_TEXTURE''.
-Here, we will use two furnace textures. They are block textures, so they must be loaded from the block atlas ''SpriteAtlasTexture.BLOCK_ATLAS_TEX''.+
 <code java> <code java>
 +    // for versions before 1.21, replace `Identifier.ofVanilla` with `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 final Sprite[] sprites = new Sprite[SPRITE_IDS.length]; 
 + 
 +    // Some constants to avoid magic numbers, these need to match the SPRITE_IDS 
 +    private static final int SPRITE_SIDE = 0; 
 +    private static final int SPRITE_TOP = 1; 
 </code> </code>
  
Line 35: Line 40:
     @Override     @Override
     public Collection<Identifier> getModelDependencies() {     public Collection<Identifier> getModelDependencies() {
-        return Collections.emptyList(); // This model does not depend on other models.+        return List.of(); // This model does not depend on other models.
     }     }
  
     @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); // The textures this model (and all its model dependenciesand their dependencies, etc...!) depends on.+        // This is related to model parentsit's not required for our use case
     }     }
  
  
     @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) {
         // Get the sprites         // Get the sprites
-        for(int i = 0; i < 2; ++i) { +        for(int i = 0; i < SPRITE_IDS.length; ++i) { 
-            SPRITES[i] = textureGetter.apply(SPRITE_IDS[i]);+            sprites[i] = textureGetter.apply(SPRITE_IDS[i]);
         }         }
         // Build the mesh using the Renderer API         // Build the mesh using the Renderer API
Line 56: Line 61:
  
         for(Direction direction : Direction.values()) {         for(Direction direction : Direction.values()) {
-            int spriteIdx = direction == Direction.UP || direction == Direction.DOWN ? 0;+            // UP and DOWN share the Y axis 
 +            int spriteIdx = direction == Direction.UP || direction == Direction.DOWN ? SPRITE_TOP SPRITE_SIDE;
             // Add a new face to the mesh             // Add a new face to the 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);
             // Set the sprite of the face, must be called after .square()             // Set the sprite of the face, must be called after .square()
             // We haven't specified any UV coordinates, so we want to use the whole texture. BAKE_LOCK_UV does exactly that.             // We haven't specified any UV coordinates, so we want to use the whole texture. BAKE_LOCK_UV does exactly that.
-            emitter.spriteBake(0, SPRITES[spriteIdx], MutableQuadView.BAKE_LOCK_UV);+            emitter.spriteBake(sprites[spriteIdx], MutableQuadView.BAKE_LOCK_UV);
             // Enable texture usage             // Enable texture usage
-            emitter.spriteColor(0, -1, -1, -1, -1);+            emitter.color(-1, -1, -1, -1);
             // Add the quad to the mesh             // Add the quad to the mesh
             emitter.emit();             emitter.emit();
Line 72: Line 78:
     }     }
 </code> </code>
- 
-Note that the type parameter "''Pair''" in ''getTextureDependencies'' method is ''com.mojang.datafixers.util.Pair'' instead of ''net.minecraft.util.Pair''. 
  
 ==== BakedModel methods ==== ==== BakedModel methods ====
Line 81: Line 85:
     public List<BakedQuad> getQuads(BlockState state, Direction face, Random random) {     public List<BakedQuad> getQuads(BlockState state, Direction face, Random random) {
         // Don't need because we use FabricBakedModel instead. However, it's better to not return null in case some mod decides to call this function.         // Don't need because we use FabricBakedModel instead. However, it's better to not return null in case some mod decides to call this function.
-        return Collections.emptyList();+        return List.of();
     }     }
  
Line 106: Line 110:
     @Override     @Override
     public Sprite getParticleSprite() {     public Sprite getParticleSprite() {
-        return SPRITES[1]; // Block break particle, let's use furnace_top+        return sprites[SPRITE_TOP]; // Block break particle, let's use furnace_top
     }     }
  
Line 132: Line 136:
                  
         // We just render the mesh         // We just render the mesh
-        renderContext.meshConsumer().accept(mesh);+        mesh.outputTo(context.getEmitter());
     }     }
  
Line 142: Line 146:
 </code> </code>
  
-===== Registering the model ===== +Note: Make sure you override the ''FabricBakedModel'' methodsthe interface has ''default'' implementations!
-Let's first write a ''ModelResourceProvider'', an interface that allows you to provide an ''UnbakedModel'' before the game tries to load it from JSON. Have a look at [[https://github.com/FabricMC/fabric/blob/1.16/fabric-models-v0/src/main/java/net/fabricmc/fabric/api/client/model/ModelResourceProvider.java|the documentation]] for more details. The important part is that ''loadModelResource()'' will be called for every model.+
  
-Let's register the model under the name ''tutorial:block/four_sided_furnace''.+===== Registering the model ===== 
 +In order for the model to be rendered in game we need to register it. In order to register it you need to create a ''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)) { +        // We want to add our model when the models are loaded 
-            return new FourSidedFurnaceModel(); +        pluginContext.modifyModelOnLoad().register((original, context) -> { 
-        } else { +            // This is called for every model that is loaded, so make sure we only target ours 
-            return null+            final ModelIdentifier id = context.topLevelId(); 
-        }+            if(id != null && id.equals(FOUR_SIDED_FURNACE_MODEL)) { 
 +                return new FourSidedFurnaceModel(); 
 +            } else { 
 +                // If we don't modify the model we just return the original as-is 
 +                return original; 
 +            } 
 +        });
     }     }
 } }
 </code> </code>
  
-Now we have to register this class in the client initializer, the entry point for client-specific code.+Then you need to register the plugin we just created:
 <code java> <code java>
 @Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
Line 167: Line 178:
     @Override     @Override
     public void onInitializeClient() {     public void onInitializeClient() {
-        ModelLoadingRegistry.INSTANCE.registerResourceProvider(rm -> new TutorialModelProvider()); +        ModelLoadingPlugin.register(new TutorialModelLoadingPlugin()); 
-        + 
         /* Other client-specific initialization */         /* Other client-specific initialization */
     }     }
Line 175: Line 186:
  
 Don't forget to register this entrypoint in ''fabric.mod.json'' if you haven't done it yet: Don't forget to register this entrypoint in ''fabric.mod.json'' if you haven't done it yet:
-<code json+<code javascript
-/* ... */+
 +  [...]
   "entrypoints": {   "entrypoints": {
-    /* ... */+    [...]
     "client": [     "client": [
       "net.fabricmc.example.ExampleModClient"       "net.fabricmc.example.ExampleModClient"
     ]     ]
   },   },
 +  [...]
 +}
 </code> </code>
  
 ===== Using the model ===== ===== Using the model =====
-You can now register your block to use your new model. For example, if your block only has one block state, put this in ''assets/your_mod_id/blockstates/your_block_id.json''+You can now [[blocks|register your block]] to use your new model. We assume this block is with 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 228: Line 250:
     @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>
  
 ==== Loading the model ==== ==== Loading the model ====
-Let's update the ''ModelResourceProvider'' we created earlier:+Let's update the ''TutorialModelLoadingPlugin'' we created earlier:
 <code java> <code java>
 +
 @Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
-public class TutorialModelProvider implements ModelResourceProvider +public class TutorialModelLoadingPlugin implements ModelLoadingPlugin 
-    public static final FourSidedFurnaceModel FOUR_SIDED_FURNACE_MODEL = new FourSidedFurnaceModel(); +  public static final ModelIdentifier FOUR_SIDED_FURNACE_MODEL = new ModelIdentifier(Identifier.of("tutorial", "four_sided_furnace"), ""); 
-    public static final Identifier FOUR_SIDED_FURNACE_MODEL_BLOCK = new Identifier("tutorial:block/four_sided_furnace"); +  public static final ModelIdentifier FOUR_SIDED_FURNACE_MODEL_ITEM = new ModelIdentifier(Identifier.of("tutorial", "four_sided_furnace"), "inventory");
-    public static final Identifier FOUR_SIDED_FURNACE_MODEL_ITEM = new Identifier("tutorial:item/four_sided_furnace");+
  
-    @Override +  @Override 
-    public UnbakedModel loadModelResource(Identifier identifierModelProviderContext modelProviderContextthrows ModelProviderException +  public void onInitializeModelLoader(Context pluginContext) { 
-        if(identifier.equals(FOUR_SIDED_FURNACE_MODEL_BLOCK) || identifier.equals(FOUR_SIDED_FURNACE_MODEL_ITEM)) { +    // We want to add our model when the models are loaded 
-            return FOUR_SIDED_FURNACE_MODEL+    pluginContext.modifyModelOnLoad().register((originalcontext-> 
-        } else { +      // This is called for every model that is loaded, so make sure we only target ours 
-            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 { 
 +        // If we don't modify the model we just return the original as-is 
 +        return original; 
 +      
 +    }); 
 +  }
 } }
 </code> </code>
tutorial/custom_model.1687729304.txt.gz · Last modified: 2023/06/25 21:41 by andrew6rant