Custom Shields 26.2
Learn how to create your own shields and configure their properties.
PREREQUISITES
You must first understand how to create a tool. This guide also references data generation for recipes, item models, and item tags.
Shields can be used to defend oneself from attacks. To add a new shield to the game, you'll need an Item, two item models, a client item, recipes, item tags, and a special renderer.
Creating the Item
PREREQUISITES
For more information, see the documentation on creating items.
For this example, we will use the same repair item tag that we used in the Custom Armor and Custom Tools pages. We define the tag reference as follows:
java
public static final TagKey<Item> REPAIRS_GUIDITE_ARMOR = TagKey.create(BuiltInRegistries.ITEM.key(), Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "repairs_guidite_armor"));1
Then, we create an item id and register an item with the following components.
- Banner Patterns: Creates an item with an empty set of banner patterns.
- Repairable: Creates an item that can be repaired with the given item tag.
- Equippable/Unswappable: In the GUI, shift-clicking the item will equip it to the offhand. In the world, right-clicking with it will not equip the item.
- Blocks Attacks: Creates an item that blocks attacks. This example uses values from the vanilla shield.
- This is a delayed component, meaning that it loads after the world is loaded, allowing it to reference datapack objects like tags.
- Break Sound: When the item breaks, it will play the specified sound.
java
public static final ResourceKey<Item> GUIDITE_SHIELD = create("guidite_shield");1
java
public static final Item GUIDITE_SHIELD = register(
ModItemIds.GUIDITE_SHIELD,
ShieldItem::new,
new Item.Properties().durability(336)
.component(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY)
.repairable(GuiditeArmorMaterial.REPAIRS_GUIDITE_ARMOR)
.equippableUnswappable(EquipmentSlot.OFFHAND)
.delayedComponent(DataComponents.BLOCKS_ATTACKS, (context) -> new BlocksAttacks(0.25F, 1.0F, List.of(new BlocksAttacks.DamageReduction(90.0F, Optional.empty(), 0.0F, 1.0F)), new BlocksAttacks.ItemDamageFunction(3.0F, 1.0F, 1.0F), Optional.of(context.getOrThrow(DamageTypeTags.BYPASSES_SHIELD)), Optional.of(SoundEvents.SHIELD_BLOCK), Optional.of(SoundEvents.SHIELD_BREAK)))
.component(DataComponents.BREAK_SOUND, SoundEvents.SHIELD_BREAK)
);1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Remember to add it to a creative tab if you want to access it from the creative inventory!
java
CreativeModeTabEvents.modifyOutputEvent(CreativeModeTabs.COMBAT)
.register((creativeTab) -> creativeTab.accept(ModItems.GUIDITE_SHIELD));1
2
2
Creating the Special Renderer
We'll be using a special renderer to render the shield, rather than the normal item model.
First, we'll create a model layer location that points to where the shield model is:
java
public static final ModelLayerLocation GUIDITE_SHIELD =
new ModelLayerLocation(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "guidite_shield"), "main");1
2
2
Then, register the layer in your client initializer:
java
SpecialModelRenderers.ID_MAPPER.put(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "guidite_shield"), GuiditeShieldSpecialRenderer.Unbaked.MAP_CODEC);
ModelLayerRegistry.registerModelLayer(GuiditeShieldLayers.GUIDITE_SHIELD, ShieldModel::createLayer);1
2
2
Then, we'll create a special renderer for the item. This one is based off of the vanilla ShieldSpecialRenderer, with changes made to allow it to take in custom sprites from the client item. We'll provide those sprites to the renderer in the next section.
The renderer is complicated, so we'll break it down.
Constructor
The constructor of the renderer accepts four parameters:
- A
SpriteGetterinterface that can provide sprites fromIdentifiers. - The model we'll be using, in this case a
ShieldModel. - The base white texture (provided in the client item), provided as a
SpriteId. - The texture used when no dye or banner patterns are present, provided as a
SpriteId.
The constructor stores all four parameters as fields so that we can use them later on.
java
public class GuiditeShieldSpecialRenderer implements SpecialModelRenderer<DataComponentMap> {
// The offset applied to the model by default
public static final Transformation DEFAULT_TRANSFORMATION = new Transformation(null, null, new Vector3f(1.0F, -1.0F, -1.0F), null);
// Maps Identifiers to their Sprites
private final SpriteGetter sprites;
// What model should be used.
private final ShieldModel model;
// The base white texture (provided in the client item)
private final SpriteId baseSprite;
// The texture used when no dye or banner patterns are present (based on the path provided in the client item).
private final SpriteId baseSpriteNoPattern;
public GuiditeShieldSpecialRenderer(final SpriteGetter sprites, final ShieldModel model, final SpriteId baseSprite, final SpriteId baseSpriteNoPattern) {
this.sprites = sprites;
this.model = model;
this.baseSprite = baseSprite;
this.baseSpriteNoPattern = baseSpriteNoPattern;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Extraction
When extracting data to be rendered, we need an immutable copy of the data that only contains the information needed to render the item. We can retrieve that from the ItemStack by converting its DataComponentMap to an immutable one in extractArgument:
java
public @Nullable DataComponentMap extractArgument(final ItemStack stack) {
return stack.immutableComponents();
}1
2
3
2
3
Extents
We'll also set the extents of the model, defining the model's bounding box, which is used for rendering and animations in the model:
java
public void getExtents(final Consumer<Vector3fc> output) {
PoseStack poseStack = new PoseStack();
this.model.root().getExtentsForGui(poseStack, output);
}1
2
3
4
2
3
4
Submission
The submission process handles the logic of what to render. The shield render's logic does the following:
- Retrieve the shield's banner patterns and store them in
patterns. If the shield has no banner patterns, it is set toBannerPatternLayers.EMPTY. - Retrieve the shield's dye color and store it in
baseColor. If the shield has no dye color, this variable is set tonull. - If the shield has banner patterns or has been dyed, use the
basetexture. If not, use thebase_nopatterntexture. - Submit the shield model to be rendered, using the provided parameters and texture.
- If the shield has banner patterns, submit those as well.
- If the shield is enchanted, submit the enchantment glint.
java
public void submit(final @Nullable DataComponentMap components, final PoseStack poseStack, final SubmitNodeCollector submitNodeCollector, final int lightCoords, final int overlayCoords, final boolean hasFoil, final int outlineColor) {
BannerPatternLayers patterns = components != null ? components.getOrDefault(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY) : BannerPatternLayers.EMPTY;
DyeColor baseColor = components != null ? components.get(DataComponents.BASE_COLOR) : null;
boolean hasPatterns = !patterns.layers().isEmpty() || baseColor != null;
SpriteId sprite = hasPatterns ? this.baseSprite : this.baseSpriteNoPattern;
submitNodeCollector.submitModel(this.model, Unit.INSTANCE, poseStack, lightCoords, overlayCoords, -1, sprite, this.sprites, outlineColor, null);
if (hasPatterns) {
BannerRenderer.submitPatterns(this.sprites, poseStack, submitNodeCollector, lightCoords, overlayCoords, this.model, Unit.INSTANCE, false, Objects.requireNonNullElse(baseColor, DyeColor.WHITE), patterns, null);
}
if (hasFoil) {
submitNodeCollector.submitModel(this.model, Unit.INSTANCE, poseStack, RenderTypes.entityGlint(), lightCoords, overlayCoords, -1, this.sprites.get(sprite), 0, null);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Unbaked Models
You'll also need an unbaked model, used to reference the model renderer and provide the sprites to the model.
java
public record Unbaked(Identifier base, Identifier noPattern) implements SpecialModelRenderer.Unbaked<DataComponentMap> {
public static final MapCodec<Unbaked> MAP_CODEC = RecordCodecBuilder.mapCodec((i) -> i.group(
Identifier.CODEC.fieldOf("texture").forGetter(Unbaked::base),
Identifier.CODEC.fieldOf("no_pattern_texture").forGetter(Unbaked::noPattern)
).apply(i, Unbaked::new));
public MapCodec<Unbaked> type() {
return MAP_CODEC;
}
public GuiditeShieldSpecialRenderer bake(final SpecialModelRenderer.BakingContext context) {
return new GuiditeShieldSpecialRenderer(context.sprites(), new ShieldModel(context.entityModelSet()
.bakeLayer(GuiditeShieldLayers.GUIDITE_SHIELD)), Sheets.SHIELD_MAPPER.apply(this.base), Sheets.SHIELD_MAPPER.apply(this.noPattern));
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Creating the Model
PREREQUISITES
For more information, see the documentation on generating item models.
We'll be creating two item models - one for the normal state, and one for when the shield is blocking - and a conditional client item for the shield, with our custom textures:
INFO
These models are data-generated. For more information, see the documentation on generating item models.
java
// Paths to the vanilla shield and modded shield
Identifier vanillaShieldModelLocation = ModelLocationUtils.getModelLocation(Items.SHIELD);
Identifier modelLocation = ModelLocationUtils.getModelLocation(ModItems.GUIDITE_SHIELD);
// Item models
ModelTemplate shieldTemplate = new ModelTemplate(Optional.of(vanillaShieldModelLocation), Optional.empty(), TextureSlot.PARTICLE);
shieldTemplate.create(modelLocation, TextureMapping.singleSlot(TextureSlot.PARTICLE, new Material(ModelLocationUtils.getModelLocation(Blocks.ACACIA_PLANKS))), itemModelGenerator.modelOutput);
ModelTemplate blockingShieldTemplate = new ModelTemplate(Optional.of(vanillaShieldModelLocation.withSuffix("_blocking")), Optional.empty(), TextureSlot.PARTICLE);
blockingShieldTemplate.create(modelLocation.withSuffix("_blocking"), TextureMapping.singleSlot(TextureSlot.PARTICLE, new Material(ModelLocationUtils.getModelLocation(Blocks.ACACIA_PLANKS))), itemModelGenerator.modelOutput);
// Client Item
GuiditeShieldSpecialRenderer.Unbaked specialRenderer = new GuiditeShieldSpecialRenderer.Unbaked(
Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "guidite_shield_base"),
Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "guidite_shield_base_nopattern")
);
itemModelGenerator.itemModelOutput.accept(ModItems.GUIDITE_SHIELD, ItemModelUtils.conditional(GuiditeShieldSpecialRenderer.DEFAULT_TRANSFORMATION, ItemModelUtils.isUsingItem(),
ItemModelUtils.specialModel(modelLocation.withSuffix("_blocking"), specialRenderer),
ItemModelUtils.specialModel(modelLocation, specialRenderer)
));1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Creating the Decorated Shield Recipe
PREREQUISITES
For more information, see the documentation on generating recipes.
There are two ways to obtain our shield in survival: either crafting a normal shield, or decorating one with banner patterns.
The crafting recipe for the base shield can be whatever you want. On the other hand, the decorated shield recipe can be created in the recipe provider like this:
java
SpecialRecipeBuilder.special(
() -> new ShieldDecorationRecipe(
this.tag(ItemTags.BANNERS),
Ingredient.of(ModItems.GUIDITE_SHIELD),
new ItemStackTemplate(ModItems.GUIDITE_SHIELD))
)
.save(this.output, "shield_decoration");1
2
3
4
5
6
7
2
3
4
5
6
7
With this recipe defined, you can now put banner patterns on your shield:

Tagging Shield Items
PREREQUISITES
For more information, see the documentation on generating item tags.
You should also place your shield in the appropriate item tags:
ItemTags.DURABILITY_ENCHANTABLE, to allow it to be enchanted with Mending and Unbreaking,ConventionalItemTags.SHIELD_TOOLS, which can be used by modders for shield-specific behavior, like custom shield enchantments.
In your item tag provider, add the following lines to addTags:
java
builder(ConventionalItemTags.SHIELD_TOOLS)
.add(ModItemIds.GUIDITE_SHIELD);
builder(ItemTags.DURABILITY_ENCHANTABLE)
.add(ModItemIds.GUIDITE_SHIELD);1
2
3
4
2
3
4
That's pretty much it! If you go in-game you should see your shield in the "Combat" tab of the creative inventory menu.



