Tutorials: Creating a Simple Transfur - LtxProgrammer/Changed-Minecraft-Mod GitHub Wiki
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.
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.
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;
}
}
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);
...
}
}
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;
}
}
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);
}
}
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.
ChangedEntity
s 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;
}
}
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.
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);
...
}
}
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