This guide shows you how to load a model, spawn it on an entity, and play an animation.
Prerequisites
BetterModel plugin installed on your server
A .bbmodel file placed in plugins/BetterModel/models/
Basic Java/Kotlin knowledge
For this tutorial, we’ll assume you have a model file named demon_knight.bbmodel with an animation called attack.
Step 1: Get a Model
Load the Model
Use BetterModel.model() to retrieve a model renderer by name. The name corresponds to the .bbmodel file without the extension. import kr.toxicity.model.api.BetterModel;
import kr.toxicity.model.api.data.renderer.ModelRenderer;
import java.util.Optional;
Optional < ModelRenderer > renderer = BetterModel . model ( "demon_knight" );
if ( renderer . isEmpty ()) {
plugin . getLogger (). warning ( "Model 'demon_knight' not found!" );
return ;
}
Use BetterModel.modelOrNull() if you prefer null checks over Optional.
Understanding Model Types
BetterModel supports two types of models:
General models (BetterModel.model()) - Located in BetterModel/models/, used for entities
Player limb models (BetterModel.limb()) - Located in BetterModel/players/, used for player cosmetics
// For entity models
Optional < ModelRenderer > entityModel = BetterModel . model ( "demon_knight" );
// For player models (cosmetics)
Optional < ModelRenderer > playerModel = BetterModel . limb ( "steve" );
Step 2: Spawn the Model
You can spawn models on entities or at specific locations.
Attach to an Entity
Attach a model to a living entity (e.g., zombie, armor stand):
import kr.toxicity.model.api.BetterModel;
import kr.toxicity.model.api.bukkit.platform.BukkitAdapter;
import kr.toxicity.model.api.tracker.EntityTracker;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Zombie;
public void spawnModelOnEntity ( Entity entity) {
EntityTracker tracker = BetterModel . model ( "demon_knight" )
. map (renderer -> renderer . create ( BukkitAdapter . adapt (entity)))
. orElse ( null );
if (tracker != null ) {
plugin . getLogger (). info ( "Model attached to entity!" );
}
}
BukkitAdapter.adapt() converts Bukkit entities to BetterModel’s platform-agnostic entity wrapper.
Spawn at a Location (Dummy)
Create a standalone model at a specific location:
import kr.toxicity.model.api.tracker.DummyTracker;
import org.bukkit.Location;
public void spawnModelAtLocation ( Location location) {
DummyTracker tracker = BetterModel . model ( "demon_knight" )
. map (renderer -> renderer . create ( BukkitAdapter . adapt (location)))
. orElse ( null );
if (tracker != null ) {
plugin . getLogger (). info ( "Model spawned at location!" );
}
}
Get or Create Pattern
Use getOrCreate() to reuse existing trackers or create a new one:
public void getOrCreateModel ( Entity entity) {
EntityTracker tracker = BetterModel . model ( "demon_knight" )
. map (renderer -> renderer . getOrCreate ( BukkitAdapter . adapt (entity)))
. orElse ( null );
// This will return the existing tracker if already attached
}
Step 3: Play an Animation
Once spawned, animate the model using the tracker:
import kr.toxicity.model.api.animation.AnimationModifier;
public void playAnimation ( EntityTracker tracker) {
// Play the "attack" animation with default settings
boolean success = tracker . animate ( "attack" );
if (success) {
plugin . getLogger (). info ( "Animation started!" );
} else {
plugin . getLogger (). warning ( "Animation 'attack' not found!" );
}
}
Advanced Animation Control
Control animation speed, looping, and transitions:
public void playAdvancedAnimation ( EntityTracker tracker) {
AnimationModifier modifier = AnimationModifier . builder ()
. speed ( 1.5F ) // 1.5x speed
. loop ( true ) // Loop continuously
. transition ( 5 ) // 5-tick blend transition
. removeAfter ( false ) // Keep animation after completion
. build ();
tracker . animate ( "attack" , modifier);
}
Stop an Animation
public void stopAnimation ( EntityTracker tracker) {
tracker . stopAnimation ( "attack" );
}
Step 4: Customize Display Properties
Update the model’s appearance using TrackerUpdateAction:
import kr.toxicity.model.api.tracker.TrackerUpdateAction;
public void customizeModel ( EntityTracker tracker) {
// Apply red tint
tracker . update ( TrackerUpdateAction . tint ( 0xFF0000 ));
// Set brightness (block light: 15, sky light: 15)
tracker . update ( TrackerUpdateAction . brightness ( 15 , 15 ));
// Enable enchanted glint on all bones
tracker . update ( TrackerUpdateAction . enchant ( true ));
// Composite multiple updates
tracker . update ( TrackerUpdateAction . composite (
TrackerUpdateAction . tint ( 0x00FF00 ),
TrackerUpdateAction . glow ( true ),
TrackerUpdateAction . glowColor ( 0xFFFF00 )
));
}
Apply Updates Before Spawn
You can apply updates before the model is sent to players:
public void spawnWithCustomization ( Entity entity) {
EntityTracker tracker = BetterModel . model ( "demon_knight" )
. map (renderer -> renderer . create (
BukkitAdapter . adapt (entity),
TrackerModifier . DEFAULT ,
t -> {
// Pre-spawn customization
t . update ( TrackerUpdateAction . tint ( 0x0026FF ));
t . update ( TrackerUpdateAction . brightness ( 15 , 15 ));
}
))
. orElse ( null );
}
Complete Example
Here’s a full working example that ties everything together:
import kr.toxicity.model.api.BetterModel;
import kr.toxicity.model.api.animation.AnimationModifier;
import kr.toxicity.model.api.bukkit.platform.BukkitAdapter;
import kr.toxicity.model.api.tracker.EntityTracker;
import kr.toxicity.model.api.tracker.TrackerModifier;
import kr.toxicity.model.api.tracker.TrackerUpdateAction;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
public class ModelCommand implements CommandExecutor {
private final JavaPlugin plugin ;
public ModelCommand ( JavaPlugin plugin ) {
this . plugin = plugin;
}
@ Override
public boolean onCommand ( CommandSender sender , Command command , String label , String [] args ) {
if ( ! (sender instanceof Player player)) {
sender . sendMessage ( "Only players can use this command!" );
return true ;
}
Entity target = player . getTargetEntity ( 5 );
if (target == null ) {
player . sendMessage ( "Look at an entity!" );
return true ;
}
// Spawn model with customization
EntityTracker tracker = BetterModel . model ( "demon_knight" )
. map (renderer -> renderer . create (
BukkitAdapter . adapt (target),
TrackerModifier . DEFAULT ,
t -> {
// Apply blue tint and full brightness
t . update ( TrackerUpdateAction . composite (
TrackerUpdateAction . tint ( 0x0026FF ),
TrackerUpdateAction . brightness ( 15 , 15 )
));
}
))
. orElse ( null );
if (tracker == null ) {
player . sendMessage ( "Failed to spawn model!" );
return true ;
}
// Play attack animation with custom settings
AnimationModifier modifier = AnimationModifier . builder ()
. speed ( 1.2F )
. transition ( 3 )
. build ();
boolean animated = tracker . animate ( "attack" , modifier);
if (animated) {
player . sendMessage ( "Model spawned and animated!" );
} else {
player . sendMessage ( "Model spawned but animation not found!" );
}
return true ;
}
}
Common Patterns
Check if Entity Already Has Model
import kr.toxicity.model.api.tracker.EntityTrackerRegistry;
public boolean hasModel ( Entity entity) {
return BetterModel . registry ( BukkitAdapter . adapt (entity)). isPresent ();
}
Remove Model from Entity
public void removeModel ( EntityTracker tracker) {
tracker . close (); // Removes all display entities and cleans up
}
Listen for Animation Events
import kr.toxicity.model.api.animation.AnimationModifier;
public void playWithCallback ( EntityTracker tracker) {
tracker . animate ( "attack" , AnimationModifier . DEFAULT , () -> {
// Called when animation completes
plugin . getLogger (). info ( "Animation finished!" );
});
}
Troubleshooting
Model returns null or empty
Verify the .bbmodel file is in plugins/BetterModel/models/
Check the filename matches (case-sensitive)
Look for errors in plugins/BetterModel/logs/ or server console
Ensure the model was loaded on server start
Confirm the animation name matches exactly (case-sensitive)
Check the animation exists in the .bbmodel file
Verify the tracker is not closed or removed
Ensure players are within render distance
Check that the entity is in a loaded chunk
Verify sight tracing settings in TrackerModifier
Ensure the model has valid textures
Try adjusting view range with TrackerUpdateAction.viewRange()
Next Steps
API Reference Explore all available methods and classes
Animation System Deep dive into animation control
Player Models Learn about 12-limb player animations
Examples See real-world usage examples