This week I decided to add a bit of variety to the Hummingbird Moth. There are actually multiple different species of Hummingbird Moth, and I wanted to represent this in the mod by adding a few different varieties. Each of them has their own subtly distinct behaviours so they aren’t just re-textured versions of the original.
Hummingbird Trait
Before implementing the variants, I wanted a more generic, data-driven way to define them. At the moment the code checks the ResourceLocation
using a magic string:
if (i.getId().compareTo(new ResourceLocation(ButterfliesMod.MOD_ID, "hummingbird")) == 0){ event.registerEntityRenderer(i.get(), HummingbirdMothRenderer::new); } else { event.registerEntityRenderer(i.get(), ButterflyRenderer::new); }
This isn’t great because it means it will only work with a single butterfly unless I add more checks using magic strings.
Thankfully, I already have a solution using Traits
. I added a new HUMMINGBIRD
trait, and created a method to choose the renderer based on this trait. It uses generated code, since these events fire earlier than data being loaded into the game.
/** * Get the render provider for the specified butterfly index. * @param butterflyIndex The index of the butterfly. * @return The renderer provider to use. */ private static @NotNull EntityRendererProvider<Butterfly> getButterflyEntityRendererProvider(int butterflyIndex) { EntityRendererProvider<Butterfly> rendererProvider = ButterflyRenderer::new; // Choose a different provider based on certain butterfly traits. List<ButterflyData.Trait> traits = Arrays.asList(ButterflyInfo.TRAITS[butterflyIndex]); if (traits.contains(ButterflyData.Trait.HUMMINGBIRD)){ rendererProvider = HummingbirdMothRenderer::new; } return rendererProvider; }
Now, when in my listener for the RegisterRenderers
event, I can use this method to decide on the renderer rather than using a magic string.
// Register the butterfly renderers. List<DeferredHolder<EntityType<?>, EntityType<? extends Mob>>> butterflies = entityTypeRegistry.getButterflies(); for (int i = 0; i < butterflies.size(); ++i) { // Get the renderer provider. EntityRendererProvider<Butterfly> rendererProvider = getButterflyEntityRendererProvider(i); // Register the selected renderer provider. event.registerEntityRenderer((EntityType<Butterfly>)butterflies.get(i).get(), rendererProvider); }
Now I’m able to create hummingbird moths that use the alternative model using an entirely data-driven system.
New Hummingbirds
Now that I can make any butterfly or moth use the Hummingbird model, I’m all set up to create a few variants. The variants I settled on come from this post on Hummingbirds Plus. The three types I set upon were as follows.
Hummingbird Hawk-Moth
This is the hummingbird moth that I already implemented, but I wanted to make some changes so they are more like their real-life counterparts. First, they are DIURNAL
, so will be active during the day. Secondly, they will fly at a FAST
speed compared to other butterflies and moths.
Clearwing Hummingbird Moth
This moth will have see-through wings similar to the Glasswing Butterfly. It will also be found in Icy terrain since they can survive in colder climates.
White-Lined Sphinx Moth
These are larger than other hummingbird moths, so will be of MEDIUM
size instead of SMALL
.
After setting up the Butterfly Data with these features in mind, all I needed to do was work on the textures for the new moths. I first added antennae to the model – something I had neglected when I first designed the moth.
For the Clearwing I went with the yellow and black body based on the images from the article, and ensured that the wings were see-through.

I enjoyed designing the White-Lined Sphinx, with its lighter tone and pink highlights.

And now, after the latest release, anyone can load into a game and spawn these new variants for the world to see!

Butterfly Scrolls
There was a bug reported by h54rt about Butterfly Scrolls not dropping properly:
I’m playing 1.20.1. Butterfly scrolls disappear when you break them off walls for some reason, it only happens after I load into the same world a separate time then when I had made the scroll.. help.
I looked at this and managed to get a reproduction right away. If you place the scroll, leave the world, and reload, then it doesn’t drop again if you break it. My first though was an issue with saving/loading, but I couldn’t find any problems with this through debugging.
What I did find was that this piece of code was failing to create a valid ItemStack
:
/** * Drop a Butterfly Scroll when this gets destroyed * @param entity The entity being destroyed. */ public void dropItem(@Nullable Entity entity) { ItemStack stack = new ItemStack(itemRegistry.getButterflyScrolls().get(this.butterflyIndex).get()); this.spawnAtLocation(stack); }
I didn’t really understand what was wrong so I took a stab in the dark and changed this method to use the built in registries instead.
/** * Drop a Butterfly Scroll when this gets destroyed * @param entity The entity being destroyed. */ @Override public void dropItem(@Nullable Entity entity) { if (this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { ResourceLocation location = new ResourceLocation( ButterfliesMod.MOD_ID, ButterflyScrollItem.getRegistryId(butterflyIndex)); Item item = ForgeRegistries.ITEMS.getValue(location); if (item != null) { ItemStack stack = new ItemStack(item); this.spawnAtLocation(stack); } } }
This fixed the problem. So, all good right?
Well, no. See the thing that’s bothering me is that I don’t know why this worked. This means there is a fundamental thing about Forge’s (and NeoForge’s) registry system that I don’t understand. So now I have homework: I need to understand what exactly I’m doing wrong here, because there may be other bugs caused by this that I’m not seeing.
If anyone knows what I’m missing here, I’d love for you to leave a comment or get in touch. I’d love to get to the bottom of this.