In Part 1 we coded a butterfly entity. Now we need to put the rest of the pieces together, so we can model, render, register, and ultimately spawn butterflies in our Minecraft world!
Model
Every entity needs a model. The model tells Minecraft what shape the entity is and also how the entity animates. Our butterflies use a fairly simple model, consisting of a main body and two flapping wings.
Hierarchical Model
The ButterflyModel defines what our entity’s model looks like. To start with we define our class:
/** * A model of a butterfly. */ public class ButterflyModel extends HierarchicalModel<Butterfly> { }
We inherit from Minecraft’s HeirarchicalModel. This kind of model consists of a series of Model Parts that extend from a root. If you know animation, think of it as something like rigging a skeleton with bones.
Construction is very simple. We just take a root in the constructor, and we use that to store off some of the other model parts for easier access later.
// The root of the model. private final ModelPart root; // The body of the butterfly. private final ModelPart body; // The left wing. private final ModelPart left_wing; // The right wing. private final ModelPart right_wing; /** * Construct a butterfly model. * @param root The root part to attach the model to. */ public ButterflyModel(ModelPart root) { this.root = root; this.body = root.getChild("body"); this.left_wing = body.getChild("left_wing"); this.right_wing = body.getChild("right_wing"); }
The base class has one abstract method that we need to implement:
/** * Get the root of the model. * @return The root ModelPart */ @Override public @NotNull ModelPart root() { return this.root; }
This method tells the base class what our root part is, so it can base all its animation from that root.
Layer
Next we need to define our layer. This will be registered with the renderer and defines the actual mesh we will use to render the butterfly.
// Holds the layers for the butterfly. public static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(new ResourceLocation(ButterfliesMod.MODID, "butterfly"), "main"); /** * Create the 3D model. * @return The complete 3D model for the butterfly. */ public static LayerDefinition createBodyLayer() { MeshDefinition meshdefinition = new MeshDefinition(); PartDefinition partdefinition = meshdefinition.getRoot(); PartDefinition body = partdefinition.addOrReplaceChild("body", CubeListBuilder.create() .texOffs(0, 20).addBox(-5.0F, 0.0F, 0.0F, 10.0F, 2.0F, 2.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(1.0F, 23.0F, 0.0F, 0.0F, -1.5708F, 0.0F)); body.addOrReplaceChild("left_wing", CubeListBuilder.create() .texOffs(0, 0).addBox(-8.0F, 1.0F, 2.0F, 17.0F, 0.0F, 10.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 0.0F, 0.0F)); body.addOrReplaceChild("right_wing", CubeListBuilder.create() .texOffs(0, 10).addBox(-8.0F, 1.0F, -10.0F, 17.0F, 0.0F, 10.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 0.0F, 0.0F)); body.addOrReplaceChild("antennae", CubeListBuilder.create() .texOffs(0, 2).addBox(-8.0F, -2.0F, 0.0F, 3.0F, 2.0F, 0.0F, new CubeDeformation(0.0F)) .texOffs(0, 0).addBox(-8.0F, -2.0F, 2.0F, 3.0F, 2.0F, 0.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 0.0F, 0.0F)); return LayerDefinition.create(meshdefinition, 64, 64); }
In createBodyLayer we define several part definitions, starting with the body. The body will be the root of our heirarchical model, and we attach parts to it for each wing and the antannae. We use addBox to define the size of each model part, and their offset from the parent part.
Textures
The calls to texOff you may have noticed in the createBodyLayer method define where in our texture we get the image to render this part with. Each Model uses a single texture. Each Model Part will take some area of that texture and use it for rendering.
For example, this texture is used for reference when creating butterfly textures, which you can also see in the github repo:
The small blue box in the top left is the antannae. Each one is 2 pixels wide so the offsets we use are (0, 0) and (0, 2). The two large blue boxes are where our wing textures are, and the multiple colours at the bottom are the the body textures.
For an example of how we can use this template we can take a look at what will ultimately become the Morpho Butterfly:
As you can see the blue wings are where the big blue boxes where, and the body texture is defined at the bottom. This will be one of the many types of butterfly that we implement.
Animation
Animating the model is easy. We just rotate the wings over time. We use a sine and cosine waves to animate them going up and down based on the number of ticks they have existed. The algorithm used is based on the bat’s animation, with some minor modifications.
/** * Create a flying animation * @param entity The butterfly entity * @param limbSwing Unused * @param limbSwingAmount Unused * @param ageInTicks The current age of the entity in ticks * @param netHeadYaw unused * @param headPitch unused */ @Override public void setupAnim(@NotNull Butterfly entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) { this.body.yRot = 0.7853982F + Mth.cos(ageInTicks * 0.1F) * 0.15F; this.right_wing.xRot = Mth.sin(ageInTicks * 1.3F) * Mth.PI * 0.25F; this.left_wing.xRot = -right_wing.xRot; }
Renderer
To connect the model to Minecraft’s rendering system, we need a renderer. In our case we have a simple implementation that just tells the game which texture to apply. Creating the class is simple:
public class ButterflyRenderer extends MobRenderer<Butterfly, ButterflyModel> { /** * Bakes a new model for the renderer * @param context The current rendering context */ public ButterflyRenderer(EntityRendererProvider.Context context) { super(context, new ButterflyModel(context.bakeLayer(ButterflyModel.LAYER_LOCATION)), 0.2F); } }
We use the MobRenderer as a base class, which is used for most Mobs in the game. The constructor takes a context which we just pass to the super class. We also create a new ButterflyModel using the layer location we defined in the class. The final parameter to the super constructor is the shadow radius, which defines the radius of the butterfly. We set it to 0.2f, since butterflies are small.
Getting the texture to use is simple. We just have a list of textures to use:
// The texture locations public static ResourceLocation[] TEXTURE = { new ResourceLocation("butterflies:textures/entity/butterfly/butterfly_blue.png"), new ResourceLocation("butterflies:textures/entity/butterfly/butterfly_nyan.png"), new ResourceLocation("butterflies:textures/entity/butterfly/butterfly_purple.png"), new ResourceLocation("butterflies:textures/entity/butterfly/butterfly_purple_trim.png"), new ResourceLocation("butterflies:textures/entity/butterfly/butterfly_rainbow.png"), new ResourceLocation("butterflies:textures/entity/butterfly/butterfly_red.png"), new ResourceLocation("butterflies:textures/entity/butterfly/butterfly_seethru.png"), new ResourceLocation("butterflies:textures/entity/butterfly/butterfly_sword.png"), new ResourceLocation("butterflies:textures/entity/butterfly/butterfly_white.png"), new ResourceLocation("butterflies:textures/entity/butterfly/butterfly_peacemaker.png"), };
And an accessor method that returns one of the textures based on the variant of the butterfly:
/** * Gets the texture to use * @param entity The butterfly entity * @return The texture to use for this entity */ @Override public @NotNull ResourceLocation getTextureLocation(@NotNull Butterfly entity) { return TEXTURE[entity.getVariant()]; }
Finally we have a scale method to make the butterflies smaller. This allows us to use more detail even though butterflies are smaller than other mobs:
/** * Scale the entity down * @param entity The butterfly entity * @param poses The current entity pose * @param scale The scale that should be applied */ @Override protected void scale(@NotNull Butterfly entity, PoseStack poses, float scale) { poses.scale(0.35F, 0.35F, 0.35F); }
Registry
We have everything we need to define and render an entity now, but how do we get the game to recognise it?
The answer is that we need to register the entity with the various systems that the game use. To manage all the entities we will eventually add to the game, we create the EntityTypeRegistry:
/** * This class registers all the entities we use with Forge's Entity Type Registry */ @Mod.EventBusSubscriber(modid = ButterfliesMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) public class EntityTypeRegistry { }
The EventBusSubscriber annotation here tells Forge that we want to listen to its events in this class. We will use this later to help register various things related to entity spawning and rendering.
We create a deferred entity register:
// An instance of a deferred registry we use to register our entity types. public static final DeferredRegister<EntityType<?>> INSTANCE = DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, ButterfliesMod.MODID);
This tells Forge that we want to register Entity Types as soon as we can. We can use this to register a new entity type:
// The Butterfly entity type. public static final RegistryObject<EntityType<Butterfly>> BUTTERFLY = INSTANCE.register(Butterfly.NAME, () -> EntityType.Builder.of(Butterfly::new, MobCategory.AMBIENT) .sized(0.3f, 0.4f) .build(Butterfly.NAME));
This creates a placeholder that will get filled with a registry entry as soon as Entity Types are registered. We tell it the name of our entity (Butterfly.NAME), give it a constructor (Butterfly::new), and tell it this Mob is an ambient mob. The only other ambient mob is the bat. They spawn in a separate pool, and will usually despawn when a player moves away from an area. We also set the size of the mob, which will be small compared to other mobs.
Next we need to subscribe to various events that will happen as the game loads. The first is the renderer’s register event:
/** * Register the renderers for our entities * @param event The event information */ @SubscribeEvent public static void registerEntityRenders(final EntityRenderersEvent.RegisterRenderers event) { event.registerEntityRenderer(BUTTERFLY.get(), ButterflyRenderer::new); }
The SubscribeEvent annotation tells Forge we want to listen for, and the class we use as a parameter tells Forge what event we want to listen for. In this case it’s the EntityRenderersEvent.RegisterRenderers. Inside the method we just register the BUTTERFLY entry we created above, with the ButterflyRenderer‘s constructor.
We also need to register the entity’s attributes in the same way:
/** * Register the attributes for living entities */ @SubscribeEvent public static void registerEntityAttributes(EntityAttributeCreationEvent event) { event.put(BUTTERFLY.get(), Butterfly.createAttributes().build()); }
We can also register the rules needed to spawn a butterfly:
/** * Register entity spawn placements here * @param event The event information */ @SubscribeEvent public static void registerEntitySpawnPlacement(SpawnPlacementRegisterEvent event) { event.register(BUTTERFLY.get(), SpawnPlacements.Type.ON_GROUND, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, Butterfly::checkButterflySpawnRules, SpawnPlacementRegisterEvent.Operation.AND); }
We use ON_GROUND so the butterfly can’t spawn in water or lava. MOTION_BLOCKING_NO_LEAVES tells the game that the butterfly must spawn on a solid block that isn’t a leaf block. We give it our spawn rule method that we defined in Part 1 of this article so we can have some custom spawn rules. The final parameter is only important if we are modifying existing spawn rules, so for this case we can use anything.
Finally we need to register the butterfly’s layers so it knows how to create the model:
/** * Registers models to be used for rendering * @param event The event information */ @SubscribeEvent public static void onRegisterLayers(EntityRenderersEvent.RegisterLayerDefinitions event) { event.registerLayerDefinition(ButterflyModel.LAYER_LOCATION, ButterflyModel::createBodyLayer); }
Spawning
Spawning has gone data driven in 1.19. We don’t need to write any Java code, all we need to do is create a JSON file under resources/data/butterflies/forge/biome_modifier/:
{ "type": "forge:add_spawns", "biomes": [ "minecraft:plains", "minecraft:sunflower_plains", "minecraft:swamp", "minecraft:mangrove_swamp", "minecraft:forest", "minecraft:flower_forest", "minecraft:birch_forest", "minecraft:dark_forest", "minecraft:old_growth_birch_forest", "minecraft:old_growth_pine_taiga", "minecraft:old_growth_spruce_taiga", "minecraft:taiga", "minecraft:savanna", "minecraft:savanna_plateau", "minecraft:windswept_hills", "minecraft:windswept_forest", "minecraft:windswept_savanna", "minecraft:jungle", "minecraft:sparse_jungle", "minecraft:bamboo_jungle", "minecraft:wooded_badlands", "minecraft:meadow", "minecraft:river", "minecraft:mushroom_fields", "minecraft:lush_caves" ], "spawners": { "type": "butterflies:butterfly", "weight": 10, "minCount": 3, "maxCount": 5 } }
The type attribute tells Forge we want to add spawns. We give a list of biomes we want the new spawn to appear. With our spawners we tell it the entity we want to spawn (butterflies:butterfly). The weight tells the game how often we want the butterfly to spawn. I’ve found that a value of 10 makes them spawn frequently enough without overwhelming the game. Finally minCount and maxCount set how many spawn at a time.
Spawn Eggs
Another method of spawning butterflies we can use is spawn eggs. This is not just useful for testing, but also allows Creative players to use them. We create a new registry, similar to the EntityTypesRegistry. The only difference is that we want to register ITEMS:
/** * This class registers items with Forge's Item Registry */ @Mod.EventBusSubscriber(modid = ButterfliesMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) public class ItemRegistry { // An instance of a deferred registry we use to register items. public static final DeferredRegister<Item> INSTANCE = DeferredRegister.create(ForgeRegistries.ITEMS, ButterfliesMod.MODID); }
To add a spawn egg we don’t need to create any new classes, we can just use the existing ForgeSpawnEggItem:
// Spawn eggs private static final RegistryObject<Item> BUTTERFLY_EGG = INSTANCE.register(Butterfly.NAME, () -> new ForgeSpawnEggItem(EntityTypeRegistry.BUTTERFLY, 0xff0000, 0x000000, new Item.Properties()));
The two parameters after BUTTERFLY are hex colours that will change the colour of the spawn egg. In this case it will be red and black.
Finally we need to ensure that it shows up in the creative tabs, so we add it by subscribing to another event:
/** * Register new creative tab items * @param event The event information */ @SubscribeEvent public static void registerCreativeTabItems(CreativeModeTabEvent.BuildContents event) { if (event.getTab() == CreativeModeTabs.SPAWN_EGGS) { event.accept(BUTTERFLY_EGG); } }
We’re not completely done yet. We also need to tell the game what texture to use. We do this using a JSON file under resources/assets/butterflies/models/item.
{ "parent": "minecraft:item/template_spawn_egg" }
This just tells the game to use the generic spawn egg model.
We also need to give it a human readable name. If we don’t it will be called item.butterflies.butterfly in the inventory which looks a little bit ugly. Minecraft uses localisation files based on each language. We’ll need at least one, in this case I’m using en_us.json. We place this under resources/assets/butterflies/lang/.
{ "item.butterflies.butterfly": "Butterfly" }
Mod Startup
We’re almost done, but we need to bring it all together. Our main Mod class is still littered with example code. We remove everything except for the mod name and rewrite our constructor.
/** * Constructor - The main entry point for the mod. */ public ButterfliesMod() { final IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); // Register ourselves for server and other game events we are interested in modEventBus.register(this); // Deferred registries. EntityTypeRegistry.INSTANCE.register(modEventBus); ItemRegistry.INSTANCE.register(modEventBus); }
All we need to do here is register our registries. This is the final piece that connects everything together. The game will now know where our entities are, what renderers to use, and how to spawn them. We can now run the game and:
Butterflies!