Tutorials: Creating a Simple Transfur - LtxProgrammer/Changed-Minecraft-Mod GitHub Wiki

Overview

This tutorial will cover how to write a transfur for your own entity. This tutorial will not cover how to setup the mod environment, how to model in Blockbench, nor how to code in Java. This tutorial assumes you already understand these topics.

Creating your entity model

Although you can start from scratch, we recommend building off of an existing model. Once you have your model completed, export it to a java entity file for later in this tutorial.

Creating a ChangedEntity

ChangedEntity is the parent class for an entity that can transfur others, and has a couple of features that are required by the mod. In your mod's entity folder: create a new class (here referred to as TestEntity) that extends ChangedEntity.

You are required to specify which LatexType and TransfurMode this entity has

in TestEntity.java

// Imports omitted

public class TestEntity extends ChangedEntity {
    public TestEntity(EntityType<? extends ChangedEntity> type, Level level) {
        super(type, level);
    }

    @Override
    public LatexType getLatexType() {
        return LatexType.NEUTRAL;
    }

    @Override
    public TransfurMode getTransfurMode() {
        return TransfurMode.REPLICATION;
    }
}

Registering your EntityType

This tutorial will not go in depth on how registration works, but you can refer to the Forge Documentation for additional information. The example in this tutorial uses the DeferredRegister class to handle registration.

in ModEntities.java

// Imports omitted

public class ModEntities {
    ...
    public static final DeferredRegister<EntityType<?> REGISTRY = DeferredRegister.create(ForgeRegistries.ENTITIES, "modid");
    public static final RegistryObject<EntityType<TestEntity>> TEST_ENTITY = REGISTRY.register("test_entity",
        () -> EntityType.Builder.of(TestEntity::new, ChangedMobCategories.CHANGED).clientTrackingRange(10).sized(0.7F, 1.93F));
    ...
}

Make sure your registries are registered on the mod event bus.

in Mod.java

// Imports omitted

public class Mod {
    public Mod() {
        ...
        final IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();

        ModEntities.REGISTRY.register(modEventBus);
        ...
    }
}

Defining your entity model

With your entity created, you can now define its model to Minecraft. Inside the exported code from your model is a function called createBodyLayer(), and it is what defines the model's shape. This is the only code we care about in the exported java file. Create a new file for your model:

in TestEntityModel.java

// Imports omitted

public class TestEntityModel extends AdvancedHumanoidModel<TestEntity> implements AdvancedHumanoidModelInterface<TestEntity, TestEntityModel> {
    public static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(new ResourceLocation("modid", "test_entity"), "main");
    private final ModelPart RightLeg;
    private final ModelPart LeftLeg;
    private final ModelPart RightArm;
    private final ModelPart LeftArm;
    private final ModelPart Head;
    private final ModelPart Torso;
    private final ModelPart Tail;
    private final HumanoidAnimator<TestEntity, TestEntityModel> animator;

    public TestEntityModel(ModelPart root) {
        super(root);
        this.RightLeg = root.getChild("RightLeg");
        this.LeftLeg = root.getChild("LeftLeg");
        this.Head = root.getChild("Head");
        this.Torso = root.getChild("Torso");
        this.Tail = Torso.getChild("Tail");
        this.RightArm = root.getChild("RightArm");
        this.LeftArm = root.getChild("LeftArm");

        var tailPrimary = Tail.getChild("TailPrimary");
        var tailSecondary = tailPrimary.getChild("TailSecondary");
        var tailTertiary = tailSecondary.getChild("TailTertiary");

        var leftLowerLeg = LeftLeg.getChild("LeftLowerLeg");
        var leftFoot = leftLowerLeg.getChild("LeftFoot");
        var rightLowerLeg = RightLeg.getChild("RightLowerLeg");
        var rightFoot = rightLowerLeg.getChild("RightFoot");

        // The animator is a helper class that reduces the duplicate code the many of Changed:MC's entities have.
        // It has quite a few presets to just plug your model's parts into to bring it to life.
        animator = HumanoidAnimator.of(this).hipOffset(-1.5f)
                .addPreset(AnimatorPresets.wolfLike(
                        Head, Head.getChild("LeftEar"), Head.getChild("RightEar"),
                        Torso, LeftArm, RightArm,
                        Tail, List.of(tailPrimary, tailSecondary, tailTertiary),
                        LeftLeg, leftLowerLeg, leftFoot, leftFoot.getChild("LeftPad"), RightLeg, rightLowerLeg, rightFoot, rightFoot.getChild("RightPad")));
    }

    public static LayerDefinition createBodyLayer() {
        // Code from exported model goes here
    }

    @Override
    public void prepareMobModel(TestEntity entity, float limbSwing, float limbSwingAmount, float partialTicks) {
        // Let the animator handle prepareMobModel()
        this.prepareMobModel(animator, entity, limbSwing, limbSwingAmount, partialTicks);
    }

    public void setupHand(TestEntity entity) {
        animator.setupHand();
    }

    @Override
    public void setupAnim(@NotNull TestEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
        animator.setupAnim(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch);
        super.setupAnim(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch);
    }

    public ModelPart getArm(HumanoidArm side) {
        return side == HumanoidArm.LEFT ? this.LeftArm : this.RightArm;
    }

    public ModelPart getLeg(HumanoidArm side) {
        return side == HumanoidArm.LEFT ? this.LeftLeg : this.RightLeg;
    }

    public ModelPart getHead() {
        return this.Head;
    }

    public ModelPart getTorso() {
        return Torso;
    }

    @Override
    public void renderToBuffer(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) {
        // Render all the root-level limbs
        RightLeg.render(poseStack, buffer, packedLight, packedOverlay, red, green, blue, alpha);
        LeftLeg.render(poseStack, buffer, packedLight, packedOverlay, red, green, blue, alpha);
        Head.render(poseStack, buffer, packedLight, packedOverlay, red, green, blue, alpha);
        Torso.render(poseStack, buffer, packedLight, packedOverlay, red, green, blue, alpha);
        RightArm.render(poseStack, buffer, packedLight, packedOverlay, red, green, blue, alpha);
        LeftArm.render(poseStack, buffer, packedLight, packedOverlay, red, green, blue, alpha);
    }

    @Override
    public HumanoidAnimator<TestEntity, TestEntityModel> getAnimator(TestEntity entity) {
        return animator;
    }
}

Registering your entity model

To be able to reference your model by name, you need to register the layer definition. This can be done by listening to the EntityRenderersEvent.RegisterLayerDefinitions event.

in ModLayerDefinitions.java

// Imports omitted

@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
public class ModLayerDefinitions {
    @SubscribeEvent
    public static void registerLayerDefinitions(EntityRenderersEvent.RegisterLayerDefinitions event) {
        event.registerLayerDefinition(ModEntityModel.LAYER_LOCATION, ModEntityModel::createBodyLayer);
    }
}

Creating your entity renderer

In this current state: the client will crash if your new entity is ever spawned in the world. This is because Minecraft attempted to render an entity that has no known renderer.

ChangedEntitys use a renderer class that helps reduce the amount of boilerplate code required for similar entities.

in ModEntityRenderer.java

// Imports omitted

public class TestEntityRenderer extends AdvancedHumanoidRenderer<TestEntity, TestEntityModel, ArmorLatexMaleWolfModel<TestEntity>> {
    private static final ResourceLocation TEXTURE = new ResourceLocation("modid", "textures/test_entity.png");

    public TestEntityRenderer (EntityRendererProvider.Context context) {
        super(context, new TestEntityModel(context.bakeLayer(TestEntityModel.LAYER_LOCATION)), ArmorLatexMaleWolfModel.MODEL_SET, 0.5f);
        this.addLayer(TransfurCapeLayer.normalCape(this, context.getModelSet()));
        this.addLayer(CustomEyesLayer.builder(this, context.getModelSet()).build());
        this.addLayer(GasMaskLayer.forSnouted(this, context.getModelSet()));
    }

    @Override
    public ResourceLocation getTextureLocation(ModEntity entity) {
        return TEXTURE;
    }
}

Registering your entity renderer

To make your renderer known to Minecraft, you can register it by listening to the EntityRenderersEvent.RegisterRenderers event.

in ModEntityRenderers.java

// Imports omitted

@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
public class ModEntityRenderers {
    @SubscribeEvent
    public static void registerEntityRenderers(EntityRenderersEvent.RegisterRenderers event) {
        registerHumanoid(event, ModEntities.TEST_ENTITY.get(), TestEntityRenderer::new);
    }
}

With that out of the way, your entity should now be visible with your model in-game.

Creating your entity's transfur variant

The Changed:MC mod wouldn't exist as it is without being able to transfur into different forms. See TransfurVariant for more details on the definition of the variant.

in ModTransfurVariants.java

public class ModTransfurVariants {
    public static final DeferredRegister<TransfurVariant<?>> REGISTRY = ChangedRegistry.TRANSFUR_VARIANT.createDeferred("modid");

    public static final RegistryObject<TransfurVariant<TestEntity>> TEST_ENTITY = REGISTRY.register("test_entity"
        () -> TransfurVariant.Builder.of(ModEntities.TEST_ENTITY).build());
}

Once again, make sure your registries are registered on the mod event bus.

in Mod.java

// Imports omitted

public class Mod {
    public Mod() {
        ...
        final IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();

        ModEntities.REGISTRY.register(modEventBus);
        ModTransfurVariants.REGISTRY.register(modEventBus);
        ...
    }
}

Conclusion

Everything should be set up now to use your newly created transfur in-game. You can use the /tf command to try it on yourself.

/tf @s modid:test_entity
⚠️ **GitHub.com Fallback** ⚠️