Skip to content

Custom Anchor Rendering

JohnSmith474 edited this page Sep 21, 2026 · 3 revisions

While a SpellFieldAnchorEntity is inherently invisible, EnchantmentCore provides a robust, data-driven pipeline to attach 2D animated sprites to it, as well as a developer-facing registry for attaching completely custom 3D rendering logic.

Default Visual Rendering (2D Sprites)

By default, passing an AnchorVisualConfig into your summon_spell_field_anchor effect payload allows you to render a 2D quad at the anchor's location. This quad is dynamically rendered at full brightness (ignoring block light) to ensure spell effects look vibrant even inside blocks or entities.

The visual config handles UV manipulation automatically based on the frame_count and ticks_per_frame properties, allowing you to easily animate your spell fields using a standard vertical sprite sheet.

AnchorVisualConfig Properties

Property Type Description
texture ResourceLocation The path to the texture sprite sheet (e.g., my_mod:textures/entity/spell/magic_circle.png).
model_id ResourceLocation An optional identifier used to bypass standard 2D rendering in favor of custom 3D logic.
scale Float The visual scaling multiplier for the quad.
frame_count Integer The total number of animation frames in the vertical sprite sheet.
ticks_per_frame Integer The duration each frame is displayed before advancing.
tint Integer An RGB color tint applied across the entire quad. (Note: If writing raw JSON, this must be a decimal integer, e.g., 16711680 for red. Hexadecimal like 0xFF0000 is only valid in Java datagen).

Custom Model Rendering (3D Geometry)

If a simple 2D animated quad is insufficient for your spell (e.g., you want to render a complex rotating 3D crystal or a localized black hole shader), you can bypass the default renderer entirely using the AnchorRendererRegistry.

The Delegation Pipeline

Because JSON files contain purely static data, Java rendering classes cannot be directly passed into an enchantment's AnchorVisualConfig. Instead, Enchantment Core uses a delegation pattern to bridge the gap between your data and your client-side rendering logic.

In Minecraft, only one renderer can be registered per entity type. Enchantment Core registers a global SpellFieldAnchorRenderer to handle every spell field anchor. Every frame, this global renderer executes the following logic:

  1. Check for a Model ID: It asks the entity if a model_id string was provided in its JSON payload and synchronized to the client.
  2. Lookup the Delegate: If a model_id exists (e.g., my_mod:crystal_anchor), the global renderer queries the AnchorRendererRegistry for a mapped Java class.
  3. Execute Custom Logic: If it finds a registered AnchorModelRenderer, it skips the default 2D rendering and delegates the rendering matrix (PoseStack) over to the custom class.
  4. Fallback: If no model_id is defined, or the registry lookup fails, it falls back to rendering the 2D sprite defined in the texture property.

1. Defining the Target

In your enchantment's JSON (or Datagen), supply a custom identifier to the model_id property of the AnchorVisualConfig:

"visual_config": {
  "model_id": "my_mod:crystal_anchor",
  "scale": 2.0
}

2. Implementing the Renderer

Create a class that implements the AnchorModelRenderer functional interface. This interface exposes the raw parameters from Minecraft's EntityRenderer pipeline.

import johnsmith.enchantmentcore.api.client.render.AnchorModelRenderer;
import johnsmith.enchantmentcore.api.entity.SpellFieldAnchor;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.entity.Entity;

public class CrystalAnchorRenderer implements AnchorModelRenderer {
    @Override
    public void render(Entity entity, float entityYaw, float partialTicks, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) {
        // Retrieve standard parameters (scale, frames, tint) if you want to utilize them
        float scale = ((SpellFieldAnchor) entity).getVisualScale();

        poseStack.pushPose();
        
        // Apply your custom transformations, rotations, and rendering logic here.
        // e.g., Rendering a BlockState, a custom GeoModel, or raw Vertex buffers.
        
        poseStack.popPose();
    }
}

3. Registering the Renderer

Register your custom renderer during your client-side setup phase, binding it to the model_id you defined in your data.

import johnsmith.enchantmentcore.api.client.render.AnchorRendererRegistry;
import net.minecraft.resources.ResourceLocation;

public class MyModClient {
    public static void init() {
        AnchorRendererRegistry.register(
            ResourceLocation.fromNamespaceAndPath("my_mod", "crystal_anchor"), 
            new CrystalAnchorRenderer()
        );
    }
}

Clone this wiki locally