Difference between revisions of "Custom Item Animations"

From Forge Community Wiki
m
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
{{Under construction}}
 
 
Custom Item animation allows items to have both 3rd person and 1st person animation. This is done by consuming custom client extension with custom rendering logic in <code>Item#initializeClient</code>.
 
Custom Item animation allows items to have both 3rd person and 1st person animation. This is done by consuming custom client extension with custom rendering logic in <code>Item#initializeClient</code>.
  
Line 32: Line 31:
  
 
== <tt>Implementing first person Item Animation</tt> ==
 
== <tt>Implementing first person Item Animation</tt> ==
In order to implmement first person Item Animation in IClientItemExtensions, override <code>#applyForgeHandTransform</code>
+
In order to implmement first person Item Animation in <code>IClientItemExtensions</code>, override <code>#applyForgeHandTransform</code>
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
 
IClientItemExtensions extension = new IClientItemExtensions() {
 
IClientItemExtensions extension = new IClientItemExtensions() {
 
     @Override
 
     @Override
 
     public boolean applyForgeHandTransform(PoseStack poseStack, LocalPlayer player, HumanoidArm arm, ItemStack itemInHand, float partialTick, float equipProcess, float swingProcess) {
 
     public boolean applyForgeHandTransform(PoseStack poseStack, LocalPlayer player, HumanoidArm arm, ItemStack itemInHand, float partialTick, float equipProcess, float swingProcess) {
     // Returning true in this method will prevent this item stack from being handled by vanilla rendering system.
+
     // Returning true in this method is recommended as it will prevent this item stack from being handled by vanilla rendering system, preventing additional unwanted transformations from being applied.
 
     }
 
     }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
The <code>applyForgeHandTransform</code> method's parameters are for:
 
The <code>applyForgeHandTransform</code> method's parameters are for:
* <code><nowiki>PoseStack poseStack</nowiki></code>: For stacking matrix poses. You can translate, rotate, scale item with it.
+
* <code><nowiki>PoseStack poseStack</nowiki></code>: For stacking matrix poses. You can translate, rotate, scale item model with it.
 
* <code><nowiki>LocalPlayer player</nowiki></code>: Main client player holding the item.
 
* <code><nowiki>LocalPlayer player</nowiki></code>: Main client player holding the item.
 
* <code><nowiki>HumanoidArm arm</nowiki></code>: For distinguishing which arm is holding the item. It's either <code>#LEFT</code> or <code>#RIGHT</code>
 
* <code><nowiki>HumanoidArm arm</nowiki></code>: For distinguishing which arm is holding the item. It's either <code>#LEFT</code> or <code>#RIGHT</code>
* <code><nowiki>ItemStack itemInHand</nowiki></code>: <code>ItemStack</code> that player is holding. Its <code>Item</code> is always the custom <code>Item</code> that you're defining currently.
+
* <code><nowiki>ItemStack itemInHand</nowiki></code>: <code>ItemStack</code> that player is holding. Its <code>Item</code> is always the custom <code>Item</code> that you're implementing animation for.
 
* <code><nowiki>float partialTick</nowiki></code>: Value ranging from 0.0 to 1.0 for telling how much time has passed since last tick. If it's close to 0.0, it means current tick just started. If it's close to 1.0, it means current tick is almost over.
 
* <code><nowiki>float partialTick</nowiki></code>: Value ranging from 0.0 to 1.0 for telling how much time has passed since last tick. If it's close to 0.0, it means current tick just started. If it's close to 1.0, it means current tick is almost over.
* <code><nowiki>float equipProcess</nowiki></code>: Value ranging from 0.0 to 1.0. If it's close to 0.0, It's close to 0.0 when the sword gauge bar is full. It's close to 1.0, when sword gauge bar is low. [[File:That_short_gage_thing_under_cross.png]]
+
* <code><nowiki>float equipProcess</nowiki></code>: Value ranging from 0.0 to 1.0. It's close to 0.0 when the sword gauge bar is full. It's close to 1.0 when sword gauge bar is low. [[File:That_short_gage_thing_under_cross.png]]
 
* <code><nowiki>float swingProcess</nowiki></code>: Value ranging from 0.0 to 1.0 for telling animation progress. If it's exactly at 0.0, the animation is not playing. If it's just close to 0.0, animation has just started playing. If it's close to 1.0, the animation is almost over.
 
* <code><nowiki>float swingProcess</nowiki></code>: Value ranging from 0.0 to 1.0 for telling animation progress. If it's exactly at 0.0, the animation is not playing. If it's just close to 0.0, animation has just started playing. If it's close to 1.0, the animation is almost over.
  
Line 59: Line 58:
 
             int i = arm == HumanoidArm.RIGHT ? 1 : -1;
 
             int i = arm == HumanoidArm.RIGHT ? 1 : -1;
 
             poseStack.translate(i * 0.56F, -0.52F, -0.72F);
 
             poseStack.translate(i * 0.56F, -0.52F, -0.72F);
            applyItemArmTransform(poseStack, arm);
+
            if (player.getUseItem() == itemInHand && player.isUsingItem()) {
            if (player.getUseItem() != itemInHand) {
+
                 poseStack.translate(0.0, -0.05, 0.0);
                 return true;
 
 
             }
 
             }
            if (player.isUsingItem()) {
+
            return true;
 +
        }
 +
    });
 +
}
 +
</syntaxhighlight>
 +
 
 +
== <tt>Implementing third person Item Animation</tt> ==
 +
Implementing third person Item Animation is done via defining custom <code>HumanoidModel$ArmPose</code> and returning it in <code>IClientItemExtensions#getArmPose</code>.
 +
 
 +
=== Defining custom ArmPose ===
 +
Forge makes <code>HumanoidModel$ArmPose</code> enum extensible, allowing you to create custom entries using <code>HumanoidModel$ArmPose#create</code>.
 +
<syntaxhighlight lang="java">
 +
private static final HumanoidModel.ArmPose EXAMPLE_POSE = HumanoidModel.ArmPose.create("EXAMPLE", false, (model, entity, arm) -> {
 +
    if (arm == HumanoidArm.RIGHT) {
 +
        model.rightArm.xRot = (float) (Math.random() * Math.PI * 2);
 +
    } else {
 +
        model.leftArm.xRot = (float) (Math.random() * Math.PI * 2);
 +
    }
 +
});
 +
</syntaxhighlight>
 +
<code>create</code> takes in 3 arguments:
 +
* <code><nowiki>String name</nowiki></code>: Name of the custom pose.
 +
* <code><nowiki>boolean twoHanded</nowiki></code>: Whether or not the animation should be two handed or single handed.
 +
* <code><nowiki>IArmPoseTransformer forgeArmPose</nowiki></code>: Functional interface for custom arm rendering logic.
 +
 
 +
after that, override <code>IClientItemExtensions#getArmPose</code> so that it returns newly created enum entry.
 +
<syntaxhighlight lang="java">
 +
@Override
 +
public HumanoidModel.ArmPose getArmPose(LivingEntity entityLiving, InteractionHand hand, ItemStack itemStack) {
 +
    if (!itemStack.isEmpty()) {
 +
        if (entityLiving.getUsedItemHand() == hand && entityLiving.getUseItemRemainingTicks() > 0) {
 +
            return EXAMPLE_POSE;
 +
        }
 +
    }
 +
    return HumanoidModel.ArmPose.EMPTY;
 +
}
 +
</syntaxhighlight>
 +
It has additional check to ensure only play custom arm rendering logic when player is actually using this item.
 +
 
 +
== Tying it all up ==
 +
Combining both examples into single <code>IClientItemExtensions</code> would look like this:
 +
 
 +
<syntaxhighlight lang="java">
 +
@Override
 +
public void initializeClient(Consumer<IClientItemExtensions> consumer)
 +
{
 +
    consumer.accept(new IClientItemExtensions() {
 +
 
 +
        private static final HumanoidModel.ArmPose EXAMPLE_POSE = HumanoidModel.ArmPose.create("EXAMPLE", false, (model, entity, arm) -> {
 +
            if (arm == HumanoidArm.RIGHT) {
 +
                model.rightArm.xRot = (float) (Math.random() * Math.PI * 2);
 +
            } else {
 +
                model.leftArm.xRot = (float) (Math.random() * Math.PI * 2);
 +
            }
 +
        });
 +
 
 +
        @Override
 +
        public HumanoidModel.ArmPose getArmPose(LivingEntity entityLiving, InteractionHand hand, ItemStack itemStack) {
 +
            if (!itemStack.isEmpty()) {
 +
                if (entityLiving.getUsedItemHand() == hand && entityLiving.getUseItemRemainingTicks() > 0) {
 +
                    return EXAMPLE_POSE;
 +
                }
 +
            }
 +
            return HumanoidModel.ArmPose.EMPTY;
 +
        }
 +
 
 +
        @Override
 +
        public boolean applyForgeHandTransform(PoseStack poseStack, LocalPlayer player, HumanoidArm arm, ItemStack itemInHand, float partialTick, float equipProcess, float swingProcess) {
 +
            int i = arm == HumanoidArm.RIGHT ? 1 : -1;
 +
            poseStack.translate(i * 0.56F, -0.52F, -0.72F);
 +
            if (player.getUseItem() == itemInHand && player.isUsingItem()) {
 
                 poseStack.translate(0.0, -0.05, 0.0);
 
                 poseStack.translate(0.0, -0.05, 0.0);
 
             }
 
             }

Latest revision as of 10:46, 4 September 2022

Custom Item animation allows items to have both 3rd person and 1st person animation. This is done by consuming custom client extension with custom rendering logic in Item#initializeClient.

Defining custom client extension

First, create a implementation of IClientItemExtensions. It can be either anonymous or named.

IClientItemExtensions extension = new IClientItemExtensions() {
// .....
}

An instance of it should be consumed in Item#initializeClient like this:

@Override
public void initializeClient(Consumer<IClientItemExtensions> consumer) {
    consumer.accept(extension);
}

Or you can define and consume implementation at same time:

@Override
public void initializeClient(Consumer<IClientItemExtensions> consumer) {
    consumer.accept(new IClientItemExtensions() {
    // .....
    });
}

Implementing first person Item Animation

In order to implmement first person Item Animation in IClientItemExtensions, override #applyForgeHandTransform

IClientItemExtensions extension = new IClientItemExtensions() {
    @Override
    public boolean applyForgeHandTransform(PoseStack poseStack, LocalPlayer player, HumanoidArm arm, ItemStack itemInHand, float partialTick, float equipProcess, float swingProcess) {
    // Returning true in this method is recommended as it will prevent this item stack from being handled by vanilla rendering system, preventing additional unwanted transformations from being applied.
    }
}

The applyForgeHandTransform method's parameters are for:

  • PoseStack poseStack: For stacking matrix poses. You can translate, rotate, scale item model with it.
  • LocalPlayer player: Main client player holding the item.
  • HumanoidArm arm: For distinguishing which arm is holding the item. It's either #LEFT or #RIGHT
  • ItemStack itemInHand: ItemStack that player is holding. Its Item is always the custom Item that you're implementing animation for.
  • float partialTick: Value ranging from 0.0 to 1.0 for telling how much time has passed since last tick. If it's close to 0.0, it means current tick just started. If it's close to 1.0, it means current tick is almost over.
  • float equipProcess: Value ranging from 0.0 to 1.0. It's close to 0.0 when the sword gauge bar is full. It's close to 1.0 when sword gauge bar is low.
    Error creating thumbnail: Unable to save thumbnail to destination
  • float swingProcess: Value ranging from 0.0 to 1.0 for telling animation progress. If it's exactly at 0.0, the animation is not playing. If it's just close to 0.0, animation has just started playing. If it's close to 1.0, the animation is almost over.

Here's example implementation:

@Override
public void initializeClient(Consumer<IClientItemExtensions> consumer) {
    consumer.accept(new IClientItemExtensions() {
        @Override
         public boolean applyForgeHandTransform(PoseStack poseStack, LocalPlayer player, HumanoidArm arm, ItemStack itemInHand, float partialTick, float equipProcess, float swingProcess) {
            int i = arm == HumanoidArm.RIGHT ? 1 : -1;
            poseStack.translate(i * 0.56F, -0.52F, -0.72F);
             if (player.getUseItem() == itemInHand && player.isUsingItem()) {
                poseStack.translate(0.0, -0.05, 0.0);
            }
            return true;
        }
    });
}

Implementing third person Item Animation

Implementing third person Item Animation is done via defining custom HumanoidModel$ArmPose and returning it in IClientItemExtensions#getArmPose.

Defining custom ArmPose

Forge makes HumanoidModel$ArmPose enum extensible, allowing you to create custom entries using HumanoidModel$ArmPose#create.

private static final HumanoidModel.ArmPose EXAMPLE_POSE = HumanoidModel.ArmPose.create("EXAMPLE", false, (model, entity, arm) -> {
    if (arm == HumanoidArm.RIGHT) {
        model.rightArm.xRot = (float) (Math.random() * Math.PI * 2);
    } else {
        model.leftArm.xRot = (float) (Math.random() * Math.PI * 2);
    }
});

create takes in 3 arguments:

  • String name: Name of the custom pose.
  • boolean twoHanded: Whether or not the animation should be two handed or single handed.
  • IArmPoseTransformer forgeArmPose: Functional interface for custom arm rendering logic.

after that, override IClientItemExtensions#getArmPose so that it returns newly created enum entry.

@Override
public HumanoidModel.ArmPose getArmPose(LivingEntity entityLiving, InteractionHand hand, ItemStack itemStack) {
    if (!itemStack.isEmpty()) {
        if (entityLiving.getUsedItemHand() == hand && entityLiving.getUseItemRemainingTicks() > 0) {
            return EXAMPLE_POSE;
        }
    }
    return HumanoidModel.ArmPose.EMPTY;
}

It has additional check to ensure only play custom arm rendering logic when player is actually using this item.

Tying it all up

Combining both examples into single IClientItemExtensions would look like this:

@Override
public void initializeClient(Consumer<IClientItemExtensions> consumer)
{
    consumer.accept(new IClientItemExtensions() {

        private static final HumanoidModel.ArmPose EXAMPLE_POSE = HumanoidModel.ArmPose.create("EXAMPLE", false, (model, entity, arm) -> {
            if (arm == HumanoidArm.RIGHT) {
                model.rightArm.xRot = (float) (Math.random() * Math.PI * 2);
            } else {
                model.leftArm.xRot = (float) (Math.random() * Math.PI * 2);
            }
        });

        @Override
        public HumanoidModel.ArmPose getArmPose(LivingEntity entityLiving, InteractionHand hand, ItemStack itemStack) {
            if (!itemStack.isEmpty()) {
                if (entityLiving.getUsedItemHand() == hand && entityLiving.getUseItemRemainingTicks() > 0) {
                    return EXAMPLE_POSE;
                }
            }
            return HumanoidModel.ArmPose.EMPTY;
        }

        @Override
        public boolean applyForgeHandTransform(PoseStack poseStack, LocalPlayer player, HumanoidArm arm, ItemStack itemInHand, float partialTick, float equipProcess, float swingProcess) {
            int i = arm == HumanoidArm.RIGHT ? 1 : -1;
            poseStack.translate(i * 0.56F, -0.52F, -0.72F);
            if (player.getUseItem() == itemInHand && player.isUsingItem()) {
                poseStack.translate(0.0, -0.05, 0.0);
            }
            return true;
        }
    });
}