Repository navigation
Java Library Creating an Entity
A Marionette mob is three classes and two registrations. This walks the worm
example (net.jelly.marionette_lib.entity.examples.worm), which is a ten-segment
chain that reaches for the nearest player.
If you rigged the mob in Blockbench, the plugin writes all three classes for you (see Exporting Java) and this page is then just a description of what you got. The only real useful section then is the last one on driving the limbs.
Implement Marionette, build your limbs in the constructor, and drive them in
tick().
public class WormEntity extends WaterAnimal implements Marionette {
private final Limb<MarionettePart<WormEntity>> tail;
public WormEntity(EntityType entityType, Level level) {
super(entityType, level);
tail = Limb.builder(this)
.segments(10, 0.5f, 0.5f, 2f / 8)
.build();
}
@Override
public List<Limb<?>> getLimbs() {
return List.of(tail);
}
@Override
public void tick() {
super.tick();
Player nearest = this.level().getNearestPlayer(this, 200);
if (nearest != null) tail.animator().setFabrikTarget(nearest.position());
tickMarionette(); // solves every limb and ticks every part
}
}Forge's Entity already defines concrete versions of three methods, so the
interface defaults lose to them and you have to forward them by hand (copy directly):
@Override
public boolean isMultipartEntity() {
return true;
}
@Override
public PartEntity<?>[] getParts() {
return getMarionetteParts();
}
@Override
public void remove(RemovalReason reason) {
super.remove(reason);
removeMarionette(reason);
}Set the entity's own sized(...) hitbox to cover the root only. The parts have
their own hitboxes.
Extend MarionetteModel<YourEntity> and hand the superclass the segment names
in part order: index i in the array is driven by part i of your limbs,
in the order getLimbs() returns them.
public class WormModel extends MarionetteModel<WormEntity> {
public static LayerDefinition createBodyLayer() {
MeshDefinition mesh = new MeshDefinition();
PartDefinition root = mesh.getRoot();
root.addOrReplaceChild("bone",
CubeListBuilder.create().texOffs(0, 0)
.addBox(-1.0F, -1.0F, -2.5F, 2.0F, 2.0F, 5.0F, new CubeDeformation(0.0F)),
PartPose.offset(0.0F, 24.0F, 0.0F));
// ... one per segment
return LayerDefinition.create(mesh, 32, 32);
}
public WormModel(ModelPart root) {
super(root, new String[] { "bone", "bone2", /* ... */ "bone10" });
}
}A regular MobRenderer with two overrides:
public class WormRenderer extends MobRenderer<WormEntity, WormModel> {
private static final ResourceLocation TEXTURE =
new ResourceLocation("yourmodid:textures/entity/worm.png");
public static final ModelLayerLocation WORM_LAYER =
new ModelLayerLocation(new ResourceLocation("yourmodid", "worm_layer"), "main");
public WormRenderer(EntityRendererProvider.Context context) {
super(context, new WormModel(context.bakeLayer(WORM_LAYER)), 1.0f);
}
@Override
protected void setupRotations(WormEntity entity, PoseStack poseStack, float ageInTicks,
float rotationYaw, float partialTicks) {
// segment positions are already world-space, so the entity's own yaw must not be
// applied on top. the 180 turn matches Blockbench's +Z forward.
poseStack.mulPose(Axis.YP.rotationDegrees(180));
}
@Override
public boolean shouldRender(WormEntity entity, Frustum camera, double x, double y, double z) {
// a limb can reach far outside the entity's own box; without this the whole
// model pops out of view when the root leaves the frustum.
if (super.shouldRender(entity, camera, x, y, z)) return true;
return camera.isVisible(entity.getMarionetteBoundingBoxForCulling(entity));
}
@Override
public ResourceLocation getTextureLocation(WormEntity entity) {
return TEXTURE;
}
}Standard for any Java Entity:
@SubscribeEvent
public static void registerLayer(EntityRenderersEvent.RegisterLayerDefinitions event) {
event.registerLayerDefinition(WormRenderer.WORM_LAYER, WormModel::createBodyLayer);
}
@SubscribeEvent
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
event.registerEntityRenderer(ModEntities.WORM.get(), WormRenderer::new);
}Basically the entirety of the interface for most applications is setFabrikTarget. Most of the character
comes from how you pick the target and root positions. The examples in the repo cover the range:
| Example | Pattern |
|---|---|
worm |
one chain, target = nearest player |
octopus |
two chains, each easing toward a random reachable goal and picking a new one on arrival |
wyvern |
a followRootOnly body chain, plus four legs whose roots are pinned to body segments and which step when a foot drifts too far from its resting spot, creating a crawling motion |
Two additional tools for shaping a solve:
-
animator().setRoot(v)pins the chain's root somewhere other than the entity's position, e.g. a shoulder, or another limb's segment, as the wyvern's legs do. -
animator().primeMultipart(dir)lays the chain out straight alongdirbefore a solve, biasing which way it folds. For example, priming an arm backwards is how you keep the elbow from bending the wrong way. It has no effect after the solve, since the next tick recomputes every direction. This is incredibly useful and is a way of imposing pseudo-constraints on the joints, and will receive a Blockbench plugin feature for visualization soon.
Java library
Blockbench plugin