tutorial:custom_model
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| tutorial:custom_model [2020/09/09 15:57] – [custom model] Fix resource reloading "bug" technici4n | tutorial:custom_model [2024/08/27 04:33] (current) – solidblock | ||
|---|---|---|---|
| Line 10: | Line 10: | ||
| <code java> | <code java> | ||
| + | @Environment(EnvType.CLIENT) | ||
| public class FourSidedFurnaceModel implements UnbakedModel, | public class FourSidedFurnaceModel implements UnbakedModel, | ||
| </ | </ | ||
| ==== Sprites ==== | ==== Sprites ==== | ||
| - | A '' | + | A '' |
| - | Here, we will use two furnace textures. They are block textures, so they must be loaded from the block atlas '' | + | |
| <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_TEX, new Identifier(" | + | new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, Identifier.ofVanilla(" |
| - | new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEX, new Identifier(" | + | new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, Identifier.ofVanilla(" |
| }; | }; | ||
| - | private Sprite[] | + | private |
| + | |||
| + | // 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; | ||
| </ | </ | ||
| Line 34: | Line 40: | ||
| @Override | @Override | ||
| public Collection< | public Collection< | ||
| - | return | + | return |
| } | } | ||
| @Override | @Override | ||
| - | public | + | public |
| - | | + | // This is related to model parents, it's not required for our use case |
| } | } | ||
| @Override | @Override | ||
| - | public BakedModel bake(ModelLoader loader, Function< | + | public BakedModel bake(Baker baker, Function< |
| // Get the sprites | // Get the sprites | ||
| - | for(int i = 0; i < 2; ++i) { | + | for(int i = 0; i < SPRITE_IDS.length; ++i) { |
| - | | + | |
| } | } | ||
| // Build the mesh using the Renderer API | // Build the mesh using the Renderer API | ||
| Line 55: | Line 61: | ||
| for(Direction direction : Direction.values()) { | for(Direction direction : Direction.values()) { | ||
| - | int spriteIdx = direction == Direction.UP || direction == Direction.DOWN ? 1 : 0; | + | |
| + | | ||
| // Add a new face to the mesh | // Add a new face to the mesh | ||
| emitter.square(direction, | emitter.square(direction, | ||
| // Set the sprite of the face, must be called after .square() | // Set the sprite of the face, must be called after .square() | ||
| // We haven' | // We haven' | ||
| - | emitter.spriteBake(0, SPRITES[spriteIdx], | + | emitter.spriteBake(sprites[spriteIdx], |
| // 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 73: | Line 80: | ||
| ==== BakedModel methods ==== | ==== BakedModel methods ==== | ||
| - | The methods here are not used by the Fabric Renderer, so we don't really care about the implementation. | + | 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 | + | |
| public List< | public List< | ||
| - | | + | // 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 List.of(); | ||
| } | } | ||
| @Override | @Override | ||
| public boolean useAmbientOcclusion() { | public boolean useAmbientOcclusion() { | ||
| - | return | + | return |
| } | } | ||
| @Override | @Override | ||
| - | public boolean | + | public boolean |
| return false; | return false; | ||
| } | } | ||
| @Override | @Override | ||
| - | public boolean | + | public boolean |
| return false; | return false; | ||
| } | } | ||
| @Override | @Override | ||
| - | public boolean | + | public boolean |
| return false; | return false; | ||
| } | } | ||
| @Override | @Override | ||
| - | public Sprite | + | public Sprite |
| - | return | + | return |
| } | } | ||
| Line 128: | Line 136: | ||
| | | ||
| // We just render the mesh | // We just render the mesh | ||
| - | | + | |
| } | } | ||
| Line 138: | Line 146: | ||
| </ | </ | ||
| - | ===== Registering | + | Note: Make sure you override |
| - | Let's first write a '' | + | |
| - | Let's register | + | ===== Registering |
| + | 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 '' | ||
| <code java> | <code java> | ||
| - | public class TutorialModelProvider | + | @Environment(EnvType.CLIENT) |
| - | public static final Identifier | + | public class TutorialModelLoadingPlugin |
| + | public static final ModelIdentifier | ||
| @Override | @Override | ||
| - | public | + | public |
| - | if(identifier.equals(FOUR_SIDED_FURNACE_MODEL)) { | + | |
| - | return new FourSidedFurnaceModel(); | + | pluginContext.modifyModelOnLoad().register((original, |
| - | } else { | + | // This is called for every model that is loaded, so make sure we only target ours |
| - | return | + | final ModelIdentifier id = context.topLevelId(); |
| - | } | + | |
| + | return new FourSidedFurnaceModel(); | ||
| + | } else { | ||
| + | // If we don't modify the model we just return | ||
| + | return original; | ||
| + | } | ||
| + | }); | ||
| } | } | ||
| } | } | ||
| </ | </ | ||
| - | Now we have to register | + | Then you need to register the plugin we just created: |
| <code java> | <code java> | ||
| + | @Environment(EnvType.CLIENT) | ||
| public class ExampleModClient implements ClientModInitializer { | public class ExampleModClient implements ClientModInitializer { | ||
| @Override | @Override | ||
| public void onInitializeClient() { | public void onInitializeClient() { | ||
| - | | + | |
| - | + | ||
| /* Other client-specific initialization */ | /* Other client-specific initialization */ | ||
| } | } | ||
| Line 169: | Line 186: | ||
| Don't forget to register this entrypoint in '' | Don't forget to register this entrypoint in '' | ||
| - | < | + | < |
| - | /* ... */ | + | { |
| + | [...] | ||
| " | " | ||
| - | | + | |
| " | " | ||
| - | "tutorial.path.to.ExampleModClient" | + | "net.fabricmc.example.ExampleModClient" |
| ] | ] | ||
| }, | }, | ||
| + | [...] | ||
| + | } | ||
| </ | </ | ||
| ===== Using the model ===== | ===== Using the model ===== | ||
| - | You can now register your block to use your new model. | + | You can now [[blocks|register your block]] to use your new model. |
| - | <code json> | + | |
| + | < | ||
| + | public final class TutorialBlocks { | ||
| + | [...] | ||
| + | public static final Block FOUR_SIDED_FURNACE = register(" | ||
| + | [...] | ||
| + | } | ||
| + | </ | ||
| + | <code javascript src/ | ||
| { | { | ||
| " | " | ||
| Line 198: | Line 226: | ||
| ==== Updating the model ==== | ==== Updating the model ==== | ||
| We will re-use the same model class, with just a small change: | We will re-use the same model class, with just a small change: | ||
| - | * We will need a '' | + | * We will need a '' |
| We will update our '' | We will update our '' | ||
| <code java> | <code java> | ||
| - | // The minecraft default block model | ||
| - | private static final Identifier DEFAULT_BLOCK_MODEL = new Identifier(" | ||
| - | |||
| - | private ModelTransformation transformation; | ||
| - | | ||
| - | // We need to add the default model to the dependencies | ||
| - | public Collection< | ||
| - | return Arrays.asList(DEFAULT_BLOCK_MODEL); | ||
| - | } | ||
| - | | ||
| - | // We need to add a bit of logic to the bake function | ||
| - | @Override | ||
| - | public BakedModel bake(ModelLoader loader, Function< | ||
| - | // Load the default block model | ||
| - | JsonUnbakedModel defaultBlockModel = (JsonUnbakedModel) loader.getOrLoadModel(DEFAULT_BLOCK_MODEL); | ||
| - | // Get its ModelTransformation | ||
| - | transformation = defaultBlockModel.getTransformations(); | ||
| - | | ||
| - | /* Previous code */ | ||
| - | } | ||
| - | | ||
| // We need to implement getTransformation() and getOverrides() | // We need to implement getTransformation() and getOverrides() | ||
| @Override | @Override | ||
| public ModelTransformation getTransformation() { | public ModelTransformation getTransformation() { | ||
| - | return | + | return |
| } | } | ||
| Line 243: | Line 250: | ||
| @Override | @Override | ||
| public void emitItemQuads(ItemStack itemStack, Supplier< | public void emitItemQuads(ItemStack itemStack, Supplier< | ||
| - | | + | |
| } | } | ||
| </ | </ | ||
| ==== Loading the model ==== | ==== Loading the model ==== | ||
| - | Let's update the '' | + | Let's update the '' |
| <code java> | <code java> | ||
| - | 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(" | ||
| - | public static final Identifier FOUR_SIDED_FURNACE_MODEL_ITEM = new Identifier(" | ||
| - | | + | @Environment(EnvType.CLIENT) |
| - | public | + | public |
| - | if(identifier.equals(FOUR_SIDED_FURNACE_MODEL_BLOCK) || identifier.equals(FOUR_SIDED_FURNACE_MODEL_ITEM)) { | + | public static final ModelIdentifier FOUR_SIDED_FURNACE_MODEL = new ModelIdentifier(Identifier.of(" |
| - | return | + | public static final ModelIdentifier FOUR_SIDED_FURNACE_MODEL_ITEM = new ModelIdentifier(Identifier.of(" |
| - | } else { | + | |
| - | return | + | @Override |
| - | } | + | public void onInitializeModelLoader(Context pluginContext) { |
| - | } | + | // We want to add our model when the models are loaded |
| + | pluginContext.modifyModelOnLoad().register((original, | ||
| + | // This is called for every model that is loaded, so make sure we only target ours | ||
| + | final ModelIdentifier id = context.topLevelId(); | ||
| + | | ||
| + | return | ||
| + | } else { | ||
| + | // If we don't modify the model we just return | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| } | } | ||
| </ | </ | ||
tutorial/custom_model.1599667072.txt.gz · Last modified: 2020/09/09 15:57 by technici4n