A while ago there was a thread by Sushifox on PlanetMinecraft offering to create “adaptamobs”. Based on an earlier thread about digital pets, an adaptamob is simply one of 6 types of mobs represented in 2D, but with a specific theme applied to it. I asked for a butterfly themed Iron Golem, and Sushifox delivered. I loved it so much that I just had to adapt it for the Butterflies Mod.
The image that started this quest, originally created by Sushifox, can be seen below. I decided it would be fun to add this as an uncommon variant of the Iron Golem, adding more of a butterfly theme to villages when playing Minecraft with the mob.
As for the golem’s behaviour I decided to keep things simple. It would be a rare golem that behaves like a normal iron golem, except with a different skin. Essentially it’s just here to add flavour to the mod.
Creating the Model
Every entity in Minecraft needs a model to define what it looks like, and the Butterfly Golem is no different. The definition of the model is embedded in the Java code, but there are some tools that can be used to create a model and export code you can use in your projects.
I can create a new texture for the Iron Golem model and add it as a variant using a data pack. Unfortunately, I can’t create the Butterfly Golem with a simple retexturing like this. The wings and the antennae don’t exist in the vanilla Iron Golem model, so I needed to modify its model in order to support these extra elements. I also wanted animated wings, which would require extra cubes attached to the base model. This can’t be done with textures in a data pack.
To solve this problem, I started by installing the CEM Template Loader plugin in Blockbench. This allows me to load in vanilla models, which I can then modify. I loaded in the Iron Golem model and got to work.
I started by adding a few flat cubes where the antennae and wings would be. For the wings, I made sure to use a separate bone for each of them, and set their pivot point to be in the center of the golem’s back. This would ensure that they would rotate properly when I add the animations later.
After some work I figured out where I wanted the antennae and wings to be, and I figured out how to overlap the wing textures so that i would only need to draw one wing. I wish I’d figured out how to do this sooner with the base butterfly model that is used for all the butterflies and moths in the mod.
It was time to move onto the texture itself. I used the base colours from Sushifox’s original design, but I added some texture based on the original Iron Golem. Despite them not being present in Sushifox’s image, I decided to keep the vines from the original model.
For the antennae, black just wasn’t working for me. They look great in the adaptamob, but looked out of place on the more detailed 3D model. I decided to make them brown like the golem’s eyebrows, and added a bit of shading to make them look less flat.
For the wings I also made some changes. The flat, contrasting colours work well in Sushifox’s image but, again, looked out of place in the 3D version. I modified the shape so they made more sense in 3D, then I took the base colours and applied them to the wings. After this, I blended some of the colours together, and added a bit of texture to give the wings a more interesting look in 3D.
I’m quite happy with the way it came out. Now it was time to put down the virtual paintbrush and start writing some actual code.
Animation
The model is also where some key animations are defined for the mob, so this is where I could add some basic movement to the wings.
Since the Butterfly Golem behaves the same way as a normal Iron Golem, I was able to use the IronGolem
class for its behaviour. However, as I said above, the model would need some new code in order to support the wings and the antennae.
For the model, I didn’t just take the code generated by BlockBench. If I did that I’d need to manually code all the animations for the Butterfly Golem, but why do that when there’s already code that I can use? Instead, I extended the IronGolemModel
and updated the model to support the new components.
/** * The model for a butterfly golem. */ @OnlyIn(Dist.CLIENT) public class ButterflyGolemModel extends IronGolemModel<IronGolem> { // Holds the layers for the model. public static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(new ResourceLocation(ButterfliesMod.MOD_ID, "butterfly_golem"), "main"); // The golem's wings. private final ModelPart leftWing; private final ModelPart rightWing; /** * Construction * @param modelPart The base part for the model. */ public ButterflyGolemModel(ModelPart modelPart) { super(modelPart); this.leftWing = root().getChild("left_wing"); this.rightWing = root().getChild("right_wing"); } /** * Create the layer definition that includes the wings. * @return The new layer definition. */ public static LayerDefinition createBodyLayer() { MeshDefinition meshDefinition = new MeshDefinition(); PartDefinition partDefinition = meshDefinition.getRoot(); partDefinition.addOrReplaceChild("head", CubeListBuilder.create() .texOffs(0, 0).addBox(-4.0F, -12.0F, -5.5F, 8.0F, 10.0F, 8.0F) .texOffs(24, 0).addBox(-1.0F, -5.0F, -7.5F, 2.0F, 4.0F, 2.0F) .texOffs(0, 18).addBox(-7.0F, -15.0F, -1.0F, 14.0F, 3.0F, 0.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, -7.0F, -2.0F)); partDefinition.addOrReplaceChild("body", CubeListBuilder.create() .texOffs(0, 40).addBox(-9.0F, -2.0F, -6.0F, 18.0F, 12.0F, 11.0F) .texOffs(0, 70).addBox(-4.5F, 10.0F, -3.0F, 9.0F, 5.0F, 6.0F, new CubeDeformation(0.5F)), PartPose.offset(0.0F, -7.0F, 0.0F)); partDefinition.addOrReplaceChild("right_arm", CubeListBuilder.create() .texOffs(60, 21).addBox(-13.0F, -2.5F, -3.0F, 4.0F, 30.0F, 6.0F), PartPose.offset(0.0F, -7.0F, 0.0F)); partDefinition.addOrReplaceChild("left_arm", CubeListBuilder.create() .texOffs(60, 58).addBox(9.0F, -2.5F, -3.0F, 4.0F, 30.0F, 6.0F), PartPose.offset(0.0F, -7.0F, 0.0F)); partDefinition.addOrReplaceChild("right_leg", CubeListBuilder.create() .texOffs(37, 0).addBox(-3.5F, -3.0F, -3.0F, 6.0F, 16.0F, 5.0F), PartPose.offset(-4.0F, 11.0F, 0.0F)); partDefinition.addOrReplaceChild("left_leg", CubeListBuilder.create() .texOffs(60, 0).mirror().addBox(-3.5F, -3.0F, -3.0F, 6.0F, 16.0F, 5.0F), PartPose.offset(5.0F, 11.0F, 0.0F)); partDefinition.addOrReplaceChild("left_wing", CubeListBuilder.create() .texOffs(20, 87).addBox(0.0F, -18.0F, 0.0F, 20.0F, 41.0F, 0.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, -3.0F, 5.0F, 0.0F, -0.5236F, 0.0F)); partDefinition.addOrReplaceChild("right_wing", CubeListBuilder.create() .texOffs(0, 87).addBox(-20.0F, -18.0F, 0.0F, 20.0F, 41.0F, 0.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, -3.0F, 5.0F, 0.0F, 0.5236F, 0.0F)); return LayerDefinition.create(meshDefinition, 128, 128); } }
Now we have a golem with wings that can use the animations from the base class. But, even though the golem wouldn’t fly, I didn’t want the wings to be static. So I overrode the setupAnim()
method in order to add the new animations.
/** * Animate the wings. * @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 IronGolem entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) { super.setupAnim(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch); this.rightWing.yRot = (Mth.sin(ageInTicks * 0.1F) * 0.1F) + 0.5F - (1.5F * Mth.triangleWave(limbSwing, 13.0F) * limbSwingAmount); this.leftWing.yRot = (Mth.sin(ageInTicks * 0.1F) * -0.1F) - 0.5f + (1.5F * Mth.triangleWave(limbSwing, 13.0F) * limbSwingAmount); }
First, I make sure to call the parent method to ensure the golem’s arms and legs still move. The animation algorithm itself can be broken into three parts. The first part makes the wings move even when idle:
(Mth.sin(ageInTicks * 0.1F) * 0.1F)
By using ageInTicks
this will make the wings move slightly over time. I use a sine wave to control the animation, reducing the scale of ageInTicks
to ensure the wings move slowly by default. With the sine wave, the value will oscillate between -1 and 1, giving a smooth animation in two directions.
The second part is just the adding (or deducting) of 0.5F
. This ensures that the wings are always at an angle to the body. If the wings were parallel to the body then there would be z-fighting. Z-fighting is when two textures are trying to be rendered in exactly the same place, and the game can’t decide which one takes priority. You end up with a fuzzy mess that doesn’t look pretty. By ensuring the wings are never flat against the golem’s back, we can prevent this graphical glitch.
The final part uses limbSwing
and limbSwingAmount
to make the wings move when the golem moves. This is based on the same algorithm used for the arms and legs, with some minor modifications. It uses a triangle wave, which is similar to a sine wave, except it has a much sharper and less smooth transition. This gives the movement a feeling of actual action, rather than just an idle resting animation.
(1.5F * Mth.triangleWave(limbSwing, 13.0F) * limbSwingAmount)
The final effect is that the wings will move when the golem is walking, and they will slow and almost seem to settle when the golem is stood still.
Rendering and Registering
To render the golem I need a custom renderer, since the Iron Golem’s texture is hardcoded. Normally, the model is passed to the super class through the constructor, but the IronGolem
class hides this interface. Thankfully, I can just replace the model with the ButterflyGolemModel
in our constructor and it will use the new model.
/** * A custom renderer for the butterfly golem. */ @OnlyIn(Dist.CLIENT) public class ButterflyGolemRenderer extends IronGolemRenderer { // The golem's texture location. private static final ResourceLocation TEXTURE = new ResourceLocation( ButterfliesMod.MOD_ID, "textures/entity/butterfly_golem/butterfly_golem.png"); /** * Construction - replace the iron golem model. * @param context The rendering context. */ public ButterflyGolemRenderer(EntityRendererProvider.Context context) { super(context); this.model = new ButterflyGolemModel(context.bakeLayer(ButterflyGolemModel.LAYER_LOCATION)); } /** * Override the Iron Golem texture. * @param ironGolem The golem entity. * @return The texture location. */ @NotNull @Override public ResourceLocation getTextureLocation(@NotNull IronGolem ironGolem) { return TEXTURE; } }
Now that I have most of the code in place. Since we are using the base IronGolem
to handle all the behaviour, I can register a new entity type using the classes we already have:
/** * Register a butterfly golem. * @return The new registry object. */ private RegistryObject<EntityType<IronGolem>> registerButterflyGolem() { return this.deferredRegister.register("butterfly_golem", () -> EntityType.Builder.of(IronGolem::new, MobCategory.MISC) .sized(1.4F, 2.7F) .clientTrackingRange(10) .build("butterfly_golem")); }
Now I just need to do all the things to complete the golem entity, including registering the layer location, registering the renderer, registering the entity’s attributes, creating a spawn egg, adding localisation, and creating a loot table. This is all boiler plate, and I’m working on a checklist for all of these steps to make it easier in the future.
Now I have almost everything in place. I can go into a test world, and spawn the golem using a spawn egg, and it looks great in game!
But I still need a way for these to spawn naturally.
Spawning
All of this is for naught if there is no way for the Butterfly Golem to appear in the game. I decided that it would be fun for players to come across them in villages randomly. I wanted them to be fairly uncommon, so I decided that 1 in 256 Iron Golems would be replaced by Butterfly Golems. This way the feature would be fairly unknown, but would give players a nice surprise when they come across one. Another mob they could add to their menagerie, if they have one.
To implement this, I listened for the FinalizeSpawn
event and wrote some code that would convert 1 in 256 Iron Golems. This is easy enough to do using the convertTo()
method:
/** * Holds event listeners for entities. */ public class MobSpawnEventListener { // The entity type registry. private final EntityTypeRegistry entityTypeRegistry; /** * Construction * @param forgeEventBus The event bus to register with. */ public MobSpawnEventListener(IEventBus forgeEventBus, EntityTypeRegistry entityTypeRegistry) { forgeEventBus.register(this); forgeEventBus.addListener(this::onMobSpawn); this.entityTypeRegistry = entityTypeRegistry; } /** * Occasionally replace an iron golem with a butterfly golem. * @param event The event context. */ @SuppressWarnings({"deprecation", "UnstableApiUsage", "OverrideOnly"}) private void onMobSpawn(MobSpawnEvent.FinalizeSpawn event) { // Only affect Iron Golems. if (event.getEntity().getType() == EntityType.IRON_GOLEM) { IronGolem ironGolem = (IronGolem) event.getEntity(); // 1 in 256 chance. if (ironGolem.getRandom().nextInt() % 256 == 1) { ServerLevelAccessor level = event.getLevel(); EntityType<IronGolem> entityType = entityTypeRegistry.getButterflyGolem().get(); // Check that we can convert the entity. if (ForgeEventFactory.canLivingConvert(ironGolem, entityType, (x) -> {})) { // Convert the Iron Golem to a Butterfly Golem IronGolem newMob = ironGolem.convertTo(entityType, false); // If the conversion succeeded, then finalize the spawn and notify the // server/client of the conversion event. if (newMob != null) { newMob.finalizeSpawn(level, level.getCurrentDifficultyAt(newMob.blockPosition()), MobSpawnType.CONVERSION, null, null); ForgeEventFactory.onLivingConvert(ironGolem, newMob); if (!newMob.isSilent()) { level.levelEvent(null, 1026, newMob.blockPosition(), 0); } } } } } } }
Now players will come across this new mob randomly, and could even create one themselves using pumpkins and iron blocks.
With this feature complete, I’m getting close to releasing the Villager version of the mod. But first I’m going to add a few more butterflies and an extra special moth based on one I almost mistook for a bird in Siem Reap. The mod is primarily about butterflies, so I want to keep adding a bit more variety in the actual butterflies, as well as these fun new villager-based features I’ve added recently.
And after that, I’m going to move onto creating another release that involves butterflies players may be a little afraid to encounter…