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
Next revisionBoth sides next revision
tutorial:custom_model [2020/08/12 18:29] – Add a custom model tutorial technici4ntutorial:custom_model [2023/06/25 21:41] – fix typo (EntType -> EnvType) andrew6rant
Line 1: Line 1:
-====== Creating a custom block model (DRAFT) ======+====== Rendering Blocks and Items Dynamically using a custom Model ======
 It is possible to add models to the game using block model JSON files, but it is also possible to render them through Java code. In this tutorial, we will add a four-sided furnace model to the game. It is possible to add models to the game using block model JSON files, but it is also possible to render them through Java code. In this tutorial, we will add a four-sided furnace model to the game.
 +
 +Note that models are rendered when the chunks are rebuilt. If you need more dynamic rendering, you can use a ''BlockEntityRenderer'': [[tutorial:blockentityrenderers]].
  
 ===== Creating the model ===== ===== Creating the model =====
Line 8: Line 10:
  
 <code java> <code java>
 +@Environment(EnvType.CLIENT)
 public class FourSidedFurnaceModel implements UnbakedModel, BakedModel, FabricBakedModel { public class FourSidedFurnaceModel implements UnbakedModel, BakedModel, FabricBakedModel {
 </code> </code>
Line 16: Line 19:
 <code java> <code java>
     private static final SpriteIdentifier[] SPRITE_IDS = new SpriteIdentifier[]{     private static final SpriteIdentifier[] SPRITE_IDS = new SpriteIdentifier[]{
-            new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEX, new Identifier("minecraft:block/furnace_front_on")), +            new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("minecraft:block/furnace_front_on")), 
-            new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEX, new Identifier("minecraft:block/furnace_top"))+            new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("minecraft:block/furnace_top"))
     };     };
     private Sprite[] SPRITES = new Sprite[2];     private Sprite[] SPRITES = new Sprite[2];
Line 28: Line 31:
 </code> </code>
  
-==== ''UnbakedModel'' methods ====+==== UnbakedModel methods ====
 <code java> <code java>
     @Override     @Override
Line 37: Line 40:
     @Override     @Override
     public Collection<SpriteIdentifier> getTextureDependencies(Function<Identifier, UnbakedModel> unbakedModelGetter, Set<Pair<String, String>> unresolvedTextureReferences) {     public Collection<SpriteIdentifier> getTextureDependencies(Function<Identifier, UnbakedModel> unbakedModelGetter, Set<Pair<String, String>> unresolvedTextureReferences) {
-        return Arrays.asList(SPRITE_IDS); // The textures this model depends on.+        return Arrays.asList(SPRITE_IDS); // The textures this model (and all its model dependencies, and their dependencies, etc...!) depends on.
     }     }
  
Line 47: Line 50:
             SPRITES[i] = textureGetter.apply(SPRITE_IDS[i]);             SPRITES[i] = textureGetter.apply(SPRITE_IDS[i]);
         }         }
-        // Build the mesh+        // Build the mesh using the Renderer API
         Renderer renderer = RendererAccess.INSTANCE.getRenderer();         Renderer renderer = RendererAccess.INSTANCE.getRenderer();
         MeshBuilder builder = renderer.meshBuilder();         MeshBuilder builder = renderer.meshBuilder();
Line 56: Line 59:
             // 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 calld after .square() +            // Set the sprite of the face, must be called after .square() 
-            emitter.spriteBake(0, SPRITES[spriteIdx], MutableQuadView.BAKE_LOCK_UV); // TODO: bake flags?+            // 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);
             // Enable texture usage             // Enable texture usage
             emitter.spriteColor(0, -1, -1, -1, -1);             emitter.spriteColor(0, -1, -1, -1, -1);
 +            // Add the quad to the mesh
             emitter.emit();             emitter.emit();
         }         }
Line 68: Line 73:
 </code> </code>
  
-==== ''BakedModel'' methods ==== +Note that the type parameter "''Pair''" in ''getTextureDependencies'' method is ''com.mojang.datafixers.util.Pair'' instead of ''net.minecraft.util.Pair''
-TODO: check this + 
-The methods here are not used by the Fabric Renderer, so we don't really care about the implementation.+==== BakedModel methods ==== 
 +Not all the methods here are used by the Fabric Renderer, so we don't really care about the implementation.
 <code java> <code java>
-@Override+    @Override
     public List<BakedQuad> getQuads(BlockState state, Direction face, Random random) {     public List<BakedQuad> getQuads(BlockState state, Direction face, Random random) {
-        return null; // Don't need because we use FabricBakedModel instead+        // 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();
     }     }
  
     @Override     @Override
     public boolean useAmbientOcclusion() {     public boolean useAmbientOcclusion() {
-        return false;+        return true// we want the block to have a shadow depending on the adjacent blocks
     }     }
  
     @Override     @Override
-    public boolean hasDepth() {+    public boolean isBuiltin() {
         return false;         return false;
     }     }
  
     @Override     @Override
-    public boolean isSideLit() {+    public boolean hasDepth() {
         return false;         return false;
     }     }
  
     @Override     @Override
-    public boolean isBuiltin() {+    public boolean isSideLit() {
         return false;         return false;
     }     }
  
     @Override     @Override
-    public Sprite getSprite() {+    public Sprite getParticleSprite() {
         return SPRITES[1]; // Block break particle, let's use furnace_top         return SPRITES[1]; // Block break particle, let's use furnace_top
     }     }
Line 113: Line 120:
 </code> </code>
  
-==== ''FabricBakedModel'' methods ====+==== FabricBakedModel methods ====
 <code java> <code java>
     @Override     @Override
Line 136: Line 143:
  
 ===== Registering the model ===== ===== Registering the model =====
-Let's first register a ''ModelResourceProvider''. Add this to your client initializerTODO add link explaining this +Let's first write a ''ModelResourceProvider'', an interface that allows you to provide an ''UnbakedModel'' before the game tries to load it from JSONHave 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 detailsThe important part is that ''loadModelResource()'' will be called for every model.
-<code java+
-ModelLoadingRegistry.INSTANCE.registerResourceProvider(rm -> new TutorialModelProvider()); +
-</code>+
  
-Let's register the model under the name ''tutorial:block/four_sided_furnace'' using the model providerTODO explain this+Let's register the model under the name ''tutorial:block/four_sided_furnace''.
 <code java> <code java>
 +@Environment(EnvType.CLIENT)
 public class TutorialModelProvider implements ModelResourceProvider { public class TutorialModelProvider implements ModelResourceProvider {
     public static final Identifier FOUR_SIDED_FURNACE_MODEL = new Identifier("tutorial:block/four_sided_furnace");     public static final Identifier FOUR_SIDED_FURNACE_MODEL = new Identifier("tutorial:block/four_sided_furnace");
Line 156: Line 161:
 </code> </code>
  
-===== Wrapping up ===== +Now we have to register this class in the client initializer, the entry point for client-specific code. 
-You can now register your block to use our new model, for example if your block only has one block state, put this in `assets/your_mod_id/blockstates/your_block_id.json`.+<code java> 
 +@Environment(EnvType.CLIENT) 
 +public class ExampleModClient implements ClientModInitializer { 
 +    @Override 
 +    public void onInitializeClient() { 
 +        ModelLoadingRegistry.INSTANCE.registerResourceProvider(rm -> new TutorialModelProvider()); 
 +         
 +        /* Other client-specific initialization */ 
 +    } 
 +
 +</code> 
 + 
 +Don't forget to register this entrypoint in ''fabric.mod.json'' if you haven't done it yet: 
 +<code json> 
 +/* ... */ 
 +  "entrypoints":
 +    /* ... */ 
 +    "client":
 +      "net.fabricmc.example.ExampleModClient" 
 +    ] 
 +  }, 
 +</code> 
 + 
 +===== Using the model ===== 
 +You can now register your block to use your new model. For exampleif your block only has one block state, put this in ''assets/your_mod_id/blockstates/your_block_id.json''.
 <code json> <code json>
 { {
Line 164: Line 193:
   }   }
 } }
 +</code>
  
-Of course, you can implement much more complex behavior!+Of course, you can implement much more complex rendering. Have fun! 
 + 
 +{{:tutorial:four_sided_furnace_render.png?nolink&600|}} 
 + 
 +===== Rendering the item ===== 
 +As you can see in the picture, the item is not rendered correctly. Let's fix this. 
 + 
 +==== Updating the model ==== 
 +We will re-use the same model class, with just a small change: 
 +  * We will need a ''ModelTransformation'' that rotates/translates/scales the model depending on its position (in right hand, in left hand, in gui, in item frame, etc...). As we are creating a model for a regular block, we can use the transform provided by fabric in ''ModelHelper.MODEL_TRANSFORM_BLOCK''
 + 
 +We will update our ''FourSidedFurnaceModel'' class as follows: 
 +<code java> 
 +    // We need to implement getTransformation() and getOverrides() 
 +    @Override 
 +    public ModelTransformation getTransformation() { 
 +        return ModelHelper.MODEL_TRANSFORM_BLOCK; 
 +    } 
 + 
 +    @Override 
 +    public ModelOverrideList getOverrides() { 
 +        return ModelOverrideList.EMPTY; 
 +    } 
 +     
 +    // We will also implement this method to have the correct lighting in the item rendering. Try to set this to false and you will see the difference. 
 +    @Override 
 +    public boolean isSideLit() { 
 +        return true; 
 +    } 
 +     
 +    // Finally, we can implement the item render function 
 +    @Override 
 +    public void emitItemQuads(ItemStack itemStack, Supplier<Random> supplier, RenderContext renderContext) { 
 +        renderContext.meshConsumer().accept(mesh); 
 +    } 
 +</code> 
 + 
 +==== Loading the model ==== 
 +Let's update the ''ModelResourceProvider'' we created earlier: 
 +<code java> 
 +@Environment(EnvType.CLIENT) 
 +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; 
 +        } 
 +    } 
 +
 +</code> 
 + 
 +===== Final result ===== 
 +{{:tutorial:four_sided_furnace_render_final.png?nolink&600|}} 
 + 
 +Et voilà! Enjoy! 
 + 
 +===== More dynamic rendering ===== 
 +The ''renderContext'' parameter in ''emitBlockQuads'' and ''emitItemQuads'' contains a ''QuadEmitter'' which you can use to build a model on the fly. 
 +<code java> 
 +    @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. */ 
 +    }
 </code> </code>
tutorial/custom_model.txt · Last modified: 2024/04/27 08:58 by florens