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");@Overridepublic void registerControllers(AzAnimationControllerContainer<ItemStack> animationControllerContainer) {animationControllerContainer.add(AzAnimationController.builder(this, "base_controller").build());}@Overridepublic @NotNull ResourceLocation getAnimationLocation(ItemStack animatable) {return ANIMATIONS;}}
Some highlights of the above example.
-
ANIMATIONS: This stores theResourceLocationreference to the animation JSON file. You need to replaceYOUR_MOD_IDwith 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 playpublic 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:
@Overridepublic 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 playdispatcher.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:
- Geometry file (
geo.json): Defines the 3D model of the item. - Texture file (
.png): The visual appearance of the item (applies over the geometry). - Animator: Animations for the item, provided by
ExampleItemAnimator.
Warning
There is currently a bug in the plugin which exports the displays with an offset applied, please use useNewOffset(true) in your AzItemRendererConfig builder to fix this.
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.
Warning
On NeoForge, setup events fire for every mod at the same time on parallel worker threads. Always register inside event.enqueueWork(...), which runs your code on the main thread after every mod's handler has finished, so your registrations can't race another mod's. Registering directly in the event handler can cause renderers to go missing at random between launches. Fabric runs its initializers one after another on a single thread, so no wrapper is needed there.
Fabric:
@Overridepublic 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:
@Overridepublic void onInitialize() {AzIdentityRegistry.register(YourItemRegistry.YOUR_ITEM, ...);}
NeoForge:
private void onCommonSetup(FMLCommonSetupEvent event) {event.enqueueWork(() -> {AzIdentityRegistry.register(YourItemRegistry.YOUR_ITEM, ...);});}