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
});
}
}
import kr.toxicity.model.api.BetterModel
import kr.toxicity.model.api.entity.BaseEntity
import org.bukkit.entity.Zombie
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.entity.EntitySpawnEvent
class BasicModelListener : Listener {
@EventHandler
fun onEntitySpawn(event: EntitySpawnEvent) {
val zombie = event.entity as? Zombie ?: return
// Get the model renderer and create tracker
BetterModel.model("demon_knight").ifPresent { renderer ->
val tracker = renderer.create(BaseEntity.of(zombie))
// Model is now attached and will render automatically
}
}
}
Getting or Creating Models
UsegetOrCreate() 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);
// Gets existing tracker or creates new one
val tracker = BetterModel.model("demon_knight")
.map { it.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()
);
// 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)
));
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 ofcreate()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
Optionalto 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);
}
}
import kr.toxicity.model.api.BetterModel
import kr.toxicity.model.api.entity.BaseEntity
import kr.toxicity.model.api.tracker.EntityTracker
import org.bukkit.entity.LivingEntity
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.entity.*
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
class ModelSystem : Listener {
private val trackers = ConcurrentHashMap<UUID, EntityTracker>()
@EventHandler
fun onSpawn(event: EntitySpawnEvent) {
val entity = event.entity as? LivingEntity ?: return
BetterModel.model("demon_knight").ifPresent { renderer ->
val tracker = renderer.getOrCreate(BaseEntity.of(entity))
trackers[entity.uniqueId] = tracker
// Start idle animation
tracker.animate("idle")
}
}
@EventHandler
fun onDamage(event: EntityDamageEvent) {
val tracker = trackers[event.entity.uniqueId] ?: return
// Play damage animation and apply red tint
tracker.animate("damage")
tracker.damageTint() // Built-in damage tint effect
}
@EventHandler
fun onDeath(event: EntityDeathEvent) {
val tracker = trackers.remove(event.entity.uniqueId) ?: 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
