Animating Items

Creating your custom AzItemAnimator

For this, you will create a class which extends AzItemAnimator. This class will handle the registration of animation controllers and providing the animation file's location for the animations.

Example:

public class ExampleItemAnimator extends AzItemAnimator {
private static final ResourceLocation ANIMATIONS = ResourceLocation.fromNamespaceAndPath(
YOUR_MOD_ID,
"animations/item/exampleitem.animation.json"
);
@Override
public void registerControllers(AzAnimationControllerContainer<ItemStack> animationControllerContainer) {
animationControllerContainer.add(
AzAnimationController.builder(this, "base_controller")
.build()
);
}
@Override
public @NotNull ResourceLocation getAnimationLocation(ItemStack animatable) {
return ANIMATIONS;
}
}

Some highlights of the above example.

  • ANIMATIONS: This stores the ResourceLocation reference to the animation JSON file. You need to replace YOUR_MOD_ID with your mod's ID and ensure the file path matches your JSON animation file.

  • registerControllers() Registers the animation controllers that will define the animation behavior and states for the item. You can multiple if needed.

Creating an Animation Dispatcher

This class isn't required but is highly suggested to help store your animation commands to call in other classes.

Example:

public class ExampleItemDispatcher {
private static final AzCommand FIRING_COMMAND = AzCommand.create("base_controller", "firing", AzPlayBehaviors.PLAY_ONCE);
public void firing(Entity entity, ItemStack itemStack) {
FIRING_COMMAND.sendForItem(entity, itemStack);
}
}

Accessing your Dispatcher to trigger animations

You will now define your ExampleItemDispatcher from above in your Item class.

public class ExampleItem extends Item {
// This is your class where you will setup the AzCommands/Animations you wish to play
public final ExampleItemDispatcher dispatcher;
public ExampleItem(Properties properties) {
super(properties);
// Create the instance of the class here to use later.
this.dispatcher = new ExampleItemDispatcher();
}
}

Now to use the dispatcher, you would simply call it in any method where you want it trigger for, like if I want the firing to happing on use, I would do it like so:

@Override
public void onUseTick(Level level, LivingEntity livingEntity, ItemStack stack, int remainingUseDuration) {
super.onUseTick(level, livingEntity, stack, remainingUseDuration);
if (livingEntity instanceof Player player && !level.isClientSide()) {
// This is where you now trigger an animation to play
dispatcher.firing(player, stack);
}
}

Creating your Renderer

This renderer is responsible for how your custom item is displayed in the game.

It connects the item with the following:

  1. Geometry file (geo.json): Defines the 3D model of the item.
  2. Texture file (.png): The visual appearance of the item (applies over the geometry).
  3. Animator: Animations for the item, provided by ExampleItemAnimator.
public class ExampleItemRenderer extends AzItemRenderer {
private static final ResourceLocation GEO = ResourceLocation.fromNamespaceAndPath(
YOUR_MOD_ID,
"geo/item/exampleitem.geo.json"
);
private static final ResourceLocation TEX = ResourceLocation.fromNamespaceAndPath(
YOUR_MOD_ID,
"textures/item/exampleitem.png"
);
public ExampleItemRenderer() {
super(
AzItemRendererConfig.builder(GEO, TEX)
.setAnimatorProvider(ExampleItemAnimator::new).build()
);
}
}

Different animations in first person and third person

By default an item stack has one animator, so whatever plays in first person also plays in third person. If you want the two views to play different animations at the same time (for example an aimed fire in first person while other players see a hip fire), opt in with useSeparateFirstPersonAnimator(). The first-person hand contexts then get their own animator with their own controllers, bone state and timer; third person, GUI, ground, item frames and every other context keep using the default animator.

public ExampleItemRenderer() {
super(
AzItemRendererConfig.builder(GEO, TEX)
.setAnimatorProvider(ExampleItemAnimator::new)
.useSeparateFirstPersonAnimator()
.build()
);
}

Both animators are built from the same AzItemAnimator, so they share controller names and the same animation file. Target one view when you dispatch:

public class ExampleItemDispatcher {
private static final AzCommand AIM_FIRING = AzCommand.create("base_controller", "aimfiring", AzPlayBehaviors.PLAY_ONCE);
private static final AzCommand FIRING = AzCommand.create("base_controller", "firing", AzPlayBehaviors.PLAY_ONCE);
public void firing(Entity entity, ItemStack itemStack) {
AIM_FIRING.sendForItemFirstPerson(entity, itemStack);
FIRING.sendForItemThirdPerson(entity, itemStack);
}
}

sendForItem(entity, stack) without a view still plays on both animators, and sendForItem(entity, stack, AzItemAnimatorView) lets you pick the view from a variable. For anything other than a plain first/third split, setAnimatorViewProvider(context -> ...) maps each ItemDisplayContext to a view yourself.

Registering your Renderer

Call AzItemRendererRegistry#register() in your onInitializeClient for Fabric, or inside event.enqueueWork(...) in your FMLClientSetupEvent handler for NeoForge.

Fabric:

@Override
public void onInitializeClient() {
AzItemRendererRegistry.register(ExampleItemRenderer::new, YourItemRegistry.YOUR_ITEM);
}

NeoForge:

private void onClientSetup(FMLClientSetupEvent event) {
event.enqueueWork(() -> {
AzItemRendererRegistry.register(ExampleItemRenderer::new, YourItemRegistry.YOUR_ITEM);
});
}

Registering your Armor for proper animation triggering

To ensure trigger animations work properly, you will need to also call AzIdentityRegistry#register() in your onInitialize for Fabric, or inside event.enqueueWork(...) in your FMLCommonSetupEvent handler for NeoForge. FMLCommonSetupEvent also runs in parallel across mods, so the same enqueueWork rule applies.

Fabric:

@Override
public void onInitialize() {
AzIdentityRegistry.register(YourItemRegistry.YOUR_ITEM, ...);
}

NeoForge:

private void onCommonSetup(FMLCommonSetupEvent event) {
event.enqueueWork(() -> {
AzIdentityRegistry.register(YourItemRegistry.YOUR_ITEM, ...);
});
}