One night in Siem Reap I saw what I thought was a hummingbird buzzing around some flowers. I got close to it and it was a strange sight. It zipped from flower to flower, its wings humming like a small bird. Yet its body was an insect-like carapace, and I couldn’t make out its legs.
It eventually flew away and left me confused. I brushed it off, and decided that its weird visage must have been a trick of the light (or rather, the dark). But it stuck with me and I decided to do some research the next day. That’s when I learned of the existence of the Hummingbird Hawk-Moth.
These incredible moths seem to emulate the behaviour of hummingbirds in an example of convergent evolution. Having come across one of these amazing creatures in the wild (or in the resort I was staying in at the time), I just had to emulate this experience in Bok’s Banging Butterflies.
So this fleeting encounter with the hawk-moth became the foundation for my next addition to Bok’s Banging Butterflies.”
Modelling
The hummingbird hawk-moth looks and behaves differently to other moths, so I didn’t want to reuse the butterfly model for this entity. So I set to work on a new model using BlockBench. For this model I wanted to capture the long proboscis it uses to feed on nectar, and the almost bird-like shape of its body.
It’s a unique shape compared to other moths, and a little more complex. One thing I paid particular attention to this time was the pivot points for the model. These determine the point that different parts of the model will rotate around, and is especially important to make the wings look right.
Now that this was all set up, I can just export the model to a .java
file, and use the generated code to create the model for the moth. Since the behaviours for the Hummingbird Moth will be the same as for other moths, the model will work with Butterfly
entities.
/** * The model for the hummingbird moth. */ @OnlyIn(Dist.CLIENT) public class HummingbirdMothModel extends HierarchicalModel<Butterfly> { // Holds the layers for the butterfly. public static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(new ResourceLocation(ButterfliesMod.MOD_ID, "hummingbird_moth"), "main"); /** * Construct a butterfly model. * @param root The root part to attach the model to. */ public HummingbirdMothModel(ModelPart root) { } /** * Create the 3D model. * @return The complete 3D model for the butterfly. */ public static LayerDefinition createBodyLayer() { MeshDefinition meshdefinition = new MeshDefinition(); PartDefinition partdefinition = meshdefinition.getRoot(); // The upper part of the body. PartDefinition thorax = partdefinition.addOrReplaceChild("thorax", CubeListBuilder.create().texOffs(0, 0) .addBox(-2.0F, -2.0F, -2.0F, 4.0F, 5.0F, 3.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 13.0F, 0.0F)); // The lower part of the body. PartDefinition abdomen = thorax.addOrReplaceChild("abdomen", CubeListBuilder.create().texOffs(14, 0) .addBox(-2.0F, 0.0F, 0.0F, 4.0F, 4.0F, 2.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 3.0F, -1.0F)); // The tail. abdomen.addOrReplaceChild("tail", CubeListBuilder.create().texOffs(14, 6) .addBox(-3.0F, 0.0F, 3.0F, 6.0F, 2.0F, 0.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 4.0F, -1.0F)); // The head. PartDefinition head = thorax.addOrReplaceChild("head", CubeListBuilder.create().texOffs(10, 9) .addBox(-2.0F, -3.0F, -2.0F, 4.0F, 3.0F, 3.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, -2.0F, 0.0F)); // The proboscis that feeds on flowers. head.addOrReplaceChild("proboscis", CubeListBuilder.create().texOffs(0, -2) .addBox(0.0F, -2.0F, -12.0F, 0.0F, 1.0F, 10.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 0.0F, 0.0F)); // The wings. PartDefinition wings = thorax.addOrReplaceChild("wings", CubeListBuilder.create(), PartPose.offset(0.0F, 13.0F, 0.0F)); wings.addOrReplaceChild("left_wing", CubeListBuilder.create().texOffs(0, 4) .addBox(0.0F, -3.0F, 0.0F, 0.0F, 6.0F, 5.0F, new CubeDeformation(0.0F)), PartPose.offset(-1.0F, -13.0F, 1.0F)); wings.addOrReplaceChild("right_wing", CubeListBuilder.create().texOffs(0, 4) .addBox(0.0F, -3.0F, 0.0F, 0.0F, 6.0F, 5.0F, new CubeDeformation(0.0F)), PartPose.offset(1.0F, -13.0F, 1.0F)); return LayerDefinition.create(meshdefinition, 32, 32); } /** * Get the root of the model. * @return The root ModelPart */ @Override public @NotNull ModelPart root() { return this.thorax; } }
That finishes off the model for the moth, but it still doesn’t look too pretty. It needs a nice texture to make it look good when players come across it in game.
Texturing
For the texture I used GIMP and loaded in the generated texture from BlockBench. I used some images found on Google as reference for the moth. I wanted to make sure I got the large eyes, the small wings, and the chitinous body that lays under the moth’s fur. I focused on the contrasting colours and the sharpness of the texture to blend from the fur on its back to the carapace protecting its abdomen.
I also made sure I had some texture on the proboscis rather than using a single colour for the whole thing. This would make it look more natural in game, rather than a single solid block that stands out. The overall effect gives it a distinct, lifelike look in Minecraft’s world of blocks.
After this is done I worked on the other textures I needed, using the Butterfly Checklist as a reference. These textures were simple to create, as many could be based on older textures with a few tweaks to the colours.
With the textures in place, and after generating data for the new moth, it was time to dig into the code base again.
Rendering
In order to use the new model I needed a new renderer class for the Hummingbird Moth. This class could be much simpler than the ButterflyRenderer
as I wouldn’t need to implement some hacks to fix mistakes I had made earlier.
/** * The renderer for a hummingbird moth. */ @OnlyIn(Dist.CLIENT) public class HummingbirdMothRenderer extends MobRenderer<Butterfly, HummingbirdMothModel> { /** * Bakes a new model for the renderer * @param context The current rendering context */ public HummingbirdMothRenderer(EntityRendererProvider.Context context) { super(context, new HummingbirdMothModel(context.bakeLayer(HummingbirdMothModel.LAYER_LOCATION)), 0.2F); } /** * 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 entity.getTexture(); } /** * 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) { float s = entity.getScale(); poses.scale(s, s, s); } }
To use this renderer, I update my RegisterRenderers
event listener so that the hummingbird moth would use the new class instead.
/** * Register the renderers for our entities * @param event The event information */ private void onRegisterRenderers(final EntityRenderersEvent.RegisterRenderers event) { for (RegistryObject<EntityType<? extends Butterfly>> i : entityTypeRegistry.getButterflies()) { if (i.getId().compareTo(new ResourceLocation(ButterfliesMod.MOD_ID, "light")) == 0) { event.registerEntityRenderer(i.get(), GlowButterflyRenderer::new); } else if (i.getId().compareTo(new ResourceLocation(ButterfliesMod.MOD_ID, "hummingbird")) == 0){ event.registerEntityRenderer(i.get(), HummingbirdMothRenderer::new); } else { event.registerEntityRenderer(i.get(), ButterflyRenderer::new); } } }
Now the new moth is functionally complete: they will fly around, pollinate flowers, and mate, as all other butterflies and moths will. But they are also very static. I still need to make them actually move.
Animation
To animate the moths we need to update the HummingbirdMothModel
. To do this I started by copying the setupAnim()
implementation from ButterflyModel
. But to make the Hummingbird Hawk-Moth behave like its real-world counterpart, I had to tweak its wing animations to create the rapid fluttering effect, and modify the angle that it’s body would hover at. With adjustments tweaks I created a small moth that flutters around very quickly.
/** * The model for the hummingbird moth. */ @OnlyIn(Dist.CLIENT) public class HummingbirdMothModel extends HierarchicalModel<Butterfly> { // The main body and also the root of the model. private final ModelPart thorax; // 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 HummingbirdMothModel(ModelPart root) { this.thorax = root.getChild("thorax"); // The wings. ModelPart wings = this.thorax.getChild("wings"); this.left_wing = wings.getChild("left_wing"); this.right_wing = wings.getChild("right_wing"); } /** * 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.thorax.xRot = 0.2853982F + Mth.cos(ageInTicks * 0.1F) * 0.15F; final float WING_ARC = 0.2f; // The arc that the wings will travel along. final float WING_SPEED = 13.0f; // The speed of the beat of the wings. this.right_wing.yRot = Mth.sin(ageInTicks * WING_SPEED) * Mth.PI * WING_ARC; this.left_wing.yRot = -right_wing.yRot; } }
Sound
A hummingbird hawk-moth flaps its wings so fast you can hear them hum. I wanted to emulate this by adding sound to the Hummingbird Moth. I had already added clicking to Luna Caterpillar, but my quick-and-dirty implementation led to certain amount of logspam1.
To fix this, I decided to add some new properties to the Butterfly Data that would define whether or not butterflies would make noise.
"sounds": { "butterfly": false, "caterpillar": false }
Using these we can prevent caterpillars and butterflies from trying to access sounds that don’t exist.
/** * Return an ambient sound for the caterpillar. If the sound doesn't exist * it just won't play. * @return A reference to the ambient sound. */ @Nullable @Override protected SoundEvent getAmbientSound() { if (getData().caterpillarSounds()) { return SoundEvent.createVariableRangeEvent(new ResourceLocation(ButterfliesMod.MOD_ID, ButterflyData.getSpeciesString(this))); } return super.getAmbientSound(); }
In the Butterfly
class I also disable the ambient sound when it isn’t active.
/** * Return an ambient sound for the caterpillar. If the sound doesn't exist * it just won't play. * @return A reference to the ambient sound. */ @Nullable @Override protected SoundEvent getAmbientSound() { if (getIsActive() && getData().butterflySounds()) { return SoundEvent.createVariableRangeEvent(new ResourceLocation(ButterfliesMod.MOD_ID, ButterflyData.getSpeciesString(this))); } return super.getAmbientSound(); }
Now I can load into a creative game and spawn these new moths into the world. The screenshots really don’t do them justice: when they hover in the air, their wings blur with speed, creating an almost hypnotic effect that mirrors the real-world hawk-moth.
Players will now be able to find these moths around the world alongside the many other species in the mod. Though they are mostly being added for aesthetics, they can be caught, bred, and used to pollinate flowers like most of the other butterflies and moths. And their unique size and shape makes them a more interesting catch for players.
Keep an eye out for the next version coming soon, in which you will be able to find and interact with these amazing creatures.
- Extraneous output to the log. Usually logs that report an error that aren’t actually errors. ↩︎