Skip to main content
This guide walks you through creating your first entity-based model with BetterModel.

Overview

Entity models are 3D models attached to Minecraft entities. They follow the entity’s position, rotation, and can respond to entity events like damage.

Prerequisites

1

Prepare Your Model

Create a BlockBench model and export it as .bbmodel format. Place it in BetterModel/models/ directory.
2

Add Plugin Dependency

Add BetterModel API to your build.gradle or pom.xml:
repositories {
    mavenCentral()
}

dependencies {
    compileOnly("io.github.toxicity188:bettermodel-bukkit-api:2.2.0")
}

Basic Implementation

Creating an Entity Model

Here’s how to spawn a basic entity model:
import kr.toxicity.model.api.BetterModel;
import kr.toxicity.model.api.entity.BaseEntity;
import kr.toxicity.model.api.tracker.EntityTracker;
import org.bukkit.entity.Zombie;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntitySpawnEvent;

public class BasicModelListener implements Listener {
    
    @EventHandler
    public void onEntitySpawn(EntitySpawnEvent event) {
        if (!(event.getEntity() instanceof Zombie zombie)) return;
        
        // Get the model renderer
        BetterModel.model("demon_knight").ifPresent(renderer -> {
            // Create entity tracker
            EntityTracker tracker = renderer.create(
                BaseEntity.of(zombie)
            );
            
            // Model is now attached and will render automatically
        });
    }
}

Getting or Creating Models

Use getOrCreate() to avoid creating duplicate models:
// Gets existing tracker or creates new one
EntityTracker tracker = BetterModel.model("demon_knight")
    .map(renderer -> renderer.getOrCreate(BaseEntity.of(entity)))
    .orElse(null);

Playing Animations

Basic Animation

// Play the "walk" animation
tracker.animate("walk");

// Play with custom settings
tracker.animate("attack", AnimationModifier.builder()
    .start(5)  // 5 tick fade-in
    .end(5)    // 5 tick fade-out
    .speed(1.5F) // 1.5x speed
    .build()
);

Stop Animation

// Stop specific animation
tracker.stopAnimation("walk");

// Stop all animations on filtered bones
tracker.stopAnimation(bone -> bone.name().value().contains("arm"), "attack");

Updating Model Properties

Change Colors and Effects

import kr.toxicity.model.api.tracker.TrackerUpdateAction;

// Apply red tint (RGB)
tracker.update(TrackerUpdateAction.tint(0xFF0000));

// Enable glow effect
tracker.update(TrackerUpdateAction.glow(true));

// Set custom brightness (block light, sky light)
tracker.update(TrackerUpdateAction.brightness(15, 15));

// Composite multiple updates
tracker.update(TrackerUpdateAction.composite(
    TrackerUpdateAction.glow(true),
    TrackerUpdateAction.glowColor(0x00FF00),
    TrackerUpdateAction.enchant(true)
));

Managing Model Lifecycle

Closing and Cleanup

// Close and remove model (calls despawn packets)
tracker.close();

// Just despawn without closing
tracker.despawn();

// Check if tracker is closed
if (tracker.isClosed()) {
    // Handle closed tracker
}

Handle Close Events

tracker.handleCloseEvent((t, reason) -> {
    switch (reason) {
        case REMOVE -> System.out.println("Model manually removed");
        case DESPAWN -> System.out.println("Entity despawned");
        case PLUGIN_DISABLE -> System.out.println("Plugin disabled");
    }
});

Player Visibility Control

import org.bukkit.entity.Player;

// Hide model from specific player
tracker.hide(player);

// Show model to specific player
tracker.show(player);

// Check if hidden
if (tracker.isHide(player)) {
    tracker.show(player);
}

Best Practices

  • Always use getOrCreate() instead of create() to prevent duplicate trackers
  • Store tracker references in a map keyed by entity UUID for easy retrieval
  • Close trackers when entities are removed to prevent memory leaks
  • Use Optional to safely handle cases where models don’t exist
  • Creating multiple trackers for the same entity can cause visual glitches
  • Always check if a model exists before trying to create a tracker
  • Trackers are not automatically removed when entities die - handle cleanup manually

Complete Example

import kr.toxicity.model.api.BetterModel;
import kr.toxicity.model.api.entity.BaseEntity;
import kr.toxicity.model.api.tracker.EntityTracker;
import kr.toxicity.model.api.tracker.TrackerUpdateAction;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.*;
import org.bukkit.plugin.java.JavaPlugin;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class ModelSystem implements Listener {
    
    private final Map<UUID, EntityTracker> trackers = new ConcurrentHashMap<>();
    
    @EventHandler
    public void onSpawn(EntitySpawnEvent event) {
        if (!(event.getEntity() instanceof LivingEntity entity)) return;
        
        BetterModel.model("demon_knight").ifPresent(renderer -> {
            EntityTracker tracker = renderer.getOrCreate(BaseEntity.of(entity));
            trackers.put(entity.getUniqueId(), tracker);
            
            // Start idle animation
            tracker.animate("idle");
        });
    }
    
    @EventHandler
    public void onDamage(EntityDamageEvent event) {
        EntityTracker tracker = trackers.get(event.getEntity().getUniqueId());
        if (tracker == null) return;
        
        // Play damage animation and apply red tint
        tracker.animate("damage");
        tracker.damageTint(); // Built-in damage tint effect
    }
    
    @EventHandler
    public void onDeath(EntityDeathEvent event) {
        EntityTracker tracker = trackers.remove(event.getEntity().getUniqueId());
        if (tracker == null) return;
        
        // Play death animation, then close after 2 seconds
        tracker.animate("death");
        tracker.location().taskLater(40, tracker::close);
    }
}

Next Steps

Animated NPC

Create interactive NPCs with custom animations

Custom Events

Learn about hitbox interactions and custom events

Conditional Animations

Implement state-based animation systems

API Reference

Explore the complete API documentation

Build docs developers (and LLMs) love