Overview
Bones are the individual components that make up your model. Each bone is rendered as a separate item display entity and can be manipulated independently. Hitboxes enable player interaction with specific bones.
Understanding Bones
Bone Hierarchy
Bones are organized in a parent-child tree structure:
// Get root bone
RenderedBone root = tracker . bone (bone -> bone . getParent () == null );
// Get bone's parent
RenderedBone parent = bone . getParent ();
// Get bone's children
RenderedBone [] children = bone . getChildren ();
// Get all bones in hierarchy (flattened)
Collection < RenderedBone > allBones = tracker . bones ();
Bones are identified by BoneName, which includes tags for special functionality:
src/main/java/com/example/BoneExample.java
// Get bone by name
RenderedBone head = tracker . bone ( "head" );
// Check bone tags
if ( head . name (). tagged ( BoneTags . HEAD )) {
System . out . println ( "This is a head bone" );
}
// Create bone name with tags
BoneName taggedName = BoneName . of ( "head" );
System . out . println ( "Tags: " + taggedName . tags ());
System . out . println ( "Name: " + taggedName . name ());
Common Bone Tags:
Marks head bones used for height calculation
Head bone that includes child bones in height calculation
Bone Manipulation
Changing Displayed Items
src/main/java/com/example/BoneItems.java
// Change item for a specific bone
boolean changed = tracker . tryUpdate (
(bone, predicate) -> bone . itemStack (
b -> b . name (). toString (). equals ( "weapon" ),
TransformedItemStack . of ( new ItemStack ( Material . DIAMOND_SWORD ))
),
BonePredicate . name ( "rightArm" )
);
if (changed) {
tracker . forceUpdate ( true ); // Force re-render
}
Applying Tint/Color
// Tint bone red (RGB hex)
tracker . update (
(bone, predicate) -> bone . tint (
b -> true ,
0xFF0000 // Red
),
BonePredicate . name ( "body" )
);
// Tint with transparency (ARGB)
int colorWithAlpha = 0x80FF0000 ; // 50% transparent red
tracker . update (
(bone, predicate) -> bone . tint (b -> true , colorWithAlpha),
BonePredicate . TRUE
);
// Reset tint (white)
tracker . update (
(bone, predicate) -> bone . tint (b -> true ),
BonePredicate . TRUE
);
src/main/java/com/example/BoneTransform.java
import org.joml.Vector3f;
import org.joml.Quaternionf;
// Add position offset
tracker . update (
(bone, predicate) -> bone . addPositionModifier (
b -> true ,
pos -> pos . add ( new Vector3f ( 0 , 1 , 0 )) // Move up 1 block
),
BonePredicate . name ( "head" )
);
// Add rotation
tracker . update (
(bone, predicate) -> bone . addRotationModifier (
b -> true ,
rot -> rot . rotateY (( float ) Math . toRadians ( 45 )) // Rotate 45 degrees
),
BonePredicate . name ( "rightArm" )
);
// Scale bone
bone . scale ( FloatSupplier . of ( 2.0f )); // 2x size
Applying Display Properties
// Apply custom display properties
tracker . update (
(bone, predicate) -> bone . applyAtDisplay (
b -> true ,
display -> {
display . invisible ( true ); // Hide bone
display . glowing ( true ); // Make it glow (if supported)
}
),
BonePredicate . name ( "wings" )
);
Adding Enchantment Glint
// Add enchantment effect to bone
tracker . update (
(bone, predicate) -> bone . enchant (
b -> true ,
true // Enable enchant glint
),
BonePredicate . name ( "weapon" )
);
Bone Predicates
Filter bones using BonePredicate:
// By name
BonePredicate byName = BonePredicate . name ( "head" );
// By tag
BonePredicate byTag = BonePredicate . tag ( BoneTags . HEAD );
// All bones
BonePredicate all = BonePredicate . TRUE ;
// Custom predicate
BonePredicate custom = BonePredicate . from (bone ->
bone . name (). toString (). startsWith ( "left" )
);
// Apply to filtered bones
tracker . update (
(bone, predicate) -> bone . tint (b -> true , 0xFF0000 ),
byTag
);
Hitboxes
Hitboxes enable player interaction with model parts using invisible entities.
Creating Hitboxes
src/main/java/com/example/HitboxExample.java
// Create hitbox on a bone
boolean created = tracker . createHitBox (
BaseEntity . of (entity),
HitBoxListener . builder ()
. interact (event -> {
System . out . println ( "Player interacted!" );
event . player (). sendMessage ( "You clicked the model!" );
})
. build (),
BonePredicate . name ( "head" )
);
HitBox Listeners
Listen to various hitbox events:
src/main/java/com/example/HitboxListeners.java
HitBoxListener listener = HitBoxListener . builder ()
// Left/right click
. interact (event -> {
Player player = event . player ();
ModelInteractionHand hand = event . hand ();
System . out . println ( player . getName () + " clicked with " + hand);
})
// Click at specific position
. interactAt (event -> {
Vector3f position = event . position ();
System . out . println ( "Clicked at: " + position);
})
// Damage/attack
. damage (event -> {
Player attacker = event . player ();
System . out . println ( attacker . getName () + " attacked!" );
event . cancel (); // Prevent default damage
})
// Entity mounted hitbox
. mount (((hitBox, entity) -> {
System . out . println ( entity . getName () + " mounted!" );
}))
// Entity dismounted
. dismount ((hitBox, entity) -> {
System . out . println ( entity . getName () + " dismounted!" );
})
// Hitbox created
. create (hitBox -> {
System . out . println ( "Hitbox created for: " + hitBox . groupName ());
})
// Hitbox removed
. remove (hitBox -> {
System . out . println ( "Hitbox removed" );
})
// Called every tick
. sync (hitBox -> {
// Update hitbox state
})
. build ();
// Create hitbox with listener
tracker . createHitBox (
BaseEntity . of (entity),
listener,
BonePredicate . name ( "body" )
);
Hitbox Events
Listen to specific event types:
src/main/java/com/example/HitboxEvents.java
HitBoxListener listener = HitBoxListener . builder ()
. listen ( HitBoxInteractEvent . class , event -> {
System . out . println ( "Interact event" );
})
. listen ( HitBoxDamagedEvent . class , event -> {
double damage = event . damage ();
System . out . println ( "Damaged: " + damage);
// Cancel event
if (damage > 10 ) {
event . cancel ();
}
})
. listen ( HitBoxMountEvent . class , event -> {
PlatformEntity rider = event . entity ();
System . out . println ( "Entity mounted: " + rider . name ());
})
. build ();
Getting Hitboxes
// Get hitbox from bone
RenderedBone bone = tracker . bone ( "head" );
HitBox hitBox = bone . getHitBox ();
if (hitBox != null ) {
System . out . println ( "Hitbox exists" );
System . out . println ( "Source: " + hitBox . source (). name ());
System . out . println ( "Position: " + hitBox . relativePosition ());
}
// Get or create hitbox
HitBox hitBox = tracker . hitbox (
BaseEntity . of (entity),
listener,
bone -> bone . name (). toString (). equals ( "head" )
);
Hitbox Properties
HitBox hitBox = bone . getHitBox ();
if (hitBox != null ) {
// Get position source bone
RenderedBone bone = hitBox . positionSource ();
// Get relative position
Vector3f position = hitBox . relativePosition ();
// Get bone name
BoneName name = hitBox . groupName ();
// Get source entity
PlatformEntity source = hitBox . source ();
// Get mount controller
MountController controller = hitBox . mountController ();
// Check if being controlled
if ( hitBox . hasBeenControlled ()) {
System . out . println ( "Hitbox is being ridden" );
}
}
Hitbox Mounting
Allow entities to ride hitboxes:
src/main/java/com/example/MountableHitbox.java
// Create mountable hitbox
HitBoxListener listener = HitBoxListener . builder ()
. mount ((hitBox, rider) -> {
System . out . println ( rider . name () + " mounted!" );
// Custom mount logic
})
. dismount ((hitBox, rider) -> {
System . out . println ( rider . name () + " dismounted!" );
})
. build ();
tracker . createHitBox (
BaseEntity . of (entity),
listener,
BonePredicate . name ( "seat" )
);
// Mount entity to hitbox
HitBox hitBox = tracker . bone ( "seat" ). getHitBox ();
if (hitBox != null ) {
hitBox . mount ( PlatformEntity . of (player));
// Check if mounted
if ( hitBox . hasMountDriver ()) {
System . out . println ( "Player is mounted" );
}
// Dismount
hitBox . dismount ( PlatformEntity . of (player));
// Dismount all
hitBox . dismountAll ();
}
Hitbox Visibility
// Hide hitbox from player
hitBox . hide (player);
// Show hitbox to player
hitBox . show (player);
Removing Hitboxes
// Remove hitbox safely
HitBox hitBox = bone . getHitBox ();
if (hitBox != null ) {
hitBox . removeHitBox ();
}
Advanced: Listener Hooks
Register global hitbox listeners on tracker:
src/main/java/com/example/GlobalHitboxListener.java
// Listen to hitbox creation
tracker . listenHitBox ((bone, builder) -> {
// Modify builder before hitbox is created
return builder
. interact (event -> {
System . out . println ( "Any hitbox clicked!" );
})
. damage (event -> {
event . cancel (); // Make all hitboxes invulnerable
});
});
// Listen to specific event type
tracker . listenHitBox ( HitBoxInteractEvent . class , event -> {
System . out . println ( "Hitbox clicked: " + event . getHitBox (). groupName ());
});
Bone Positions
Get world positions of bones:
import org.joml.Vector3f;
// Get world position
Vector3f position = bone . worldPosition ();
System . out . println ( "Bone at: " + position);
// Get world rotation
Vector3f rotation = bone . worldRotation ();
System . out . println ( "Bone rotation: " + rotation);
// Get hitbox position
Vector3f hitBoxPos = bone . hitBoxPosition ();
System . out . println ( "Hitbox at: " + hitBoxPos);
// Get hitbox scale
float scale = bone . hitBoxScale ();
System . out . println ( "Hitbox scale: " + scale);
Add nametags to bones:
src/main/java/com/example/Nametag.java
// Create nametag
boolean created = tracker . createNametag (
BonePredicate . name ( "head" ),
(bone, nametag) -> {
nametag . text ( "Boss Name" );
nametag . visible ( true );
// Update position every tick
tracker . perPlayerTick ((t, player) -> {
nametag . teleport ( t . location ());
nametag . send (player);
});
}
);
Best Practices
Use predicates for bulk operations
Apply changes to multiple bones efficiently: // Good: single update for all arm bones
tracker . update (
(bone, predicate) -> bone . tint (b -> true , 0xFF0000 ),
BonePredicate . from (b ->
b . name (). tagged ( BoneTags . LEFT_ARM , BoneTags . RIGHT_ARM )
)
);
Don’t look up bones repeatedly: // Cache bone reference
private final RenderedBone headBone = tracker . bone ( "head" );
public void updateHead () {
if (headBone != null ) {
headBone . tint (b -> true , 0xFF0000 );
}
}
Use forceUpdate after modifications
Ensure changes are visible: tracker . update (
(bone, predicate) -> bone . tint (b -> true , 0xFF0000 ),
BonePredicate . TRUE
);
tracker . forceUpdate ( true ); // Force re-render
Clean up hitboxes properly
Remove hitboxes when no longer needed: @ EventHandler
public void onEntityDeath ( EntityDeathEvent event) {
BetterModel . registry ( event . getEntity (). getUniqueId ())
. ifPresent (registry -> {
registry . allTrackers (). forEach (tracker -> {
tracker . bones (). forEach (bone -> {
HitBox hitBox = bone . getHitBox ();
if (hitBox != null ) {
hitBox . removeHitBox ();
}
});
});
});
}
Complete Example
src/main/java/com/example/InteractiveNPC.java
public class InteractiveNPC {
private final EntityTracker tracker ;
public InteractiveNPC ( EntityTracker tracker , Entity entity ) {
this . tracker = tracker;
// Make head interactive
tracker . createHitBox (
BaseEntity . of (entity),
HitBoxListener . builder ()
. interact (event -> {
event . player (). sendMessage ( "You clicked my head!" );
playHeadAnimation ();
})
. build (),
BonePredicate . tag ( BoneTags . HEAD )
);
// Make body damageable
tracker . createHitBox (
BaseEntity . of (entity),
HitBoxListener . builder ()
. damage (event -> {
event . player (). sendMessage ( "Ouch!" );
flashRed ();
})
. build (),
BonePredicate . name ( "body" )
);
// Display weapon in right hand
tracker . update (
(bone, predicate) -> bone . itemStack (
b -> true ,
TransformedItemStack . of ( new ItemStack ( Material . DIAMOND_SWORD ))
),
BonePredicate . name ( "rightArm" )
);
}
private void playHeadAnimation () {
tracker . animate ( "nod" , AnimationModifier . DEFAULT_WITH_PLAY_ONCE );
}
private void flashRed () {
// Flash red for 10 ticks
tracker . update (
(bone, predicate) -> bone . tint (b -> true , 0xFF0000 ),
BonePredicate . TRUE
);
Bukkit . getScheduler (). runTaskLater (plugin, () -> {
// Reset tint
tracker . update (
(bone, predicate) -> bone . tint (b -> true ),
BonePredicate . TRUE
);
}, 10 );
}
}
See Also
Trackers Managing tracker lifecycle
Animations Per-bone animation control
Custom Events Responding to hitbox events
API Reference Complete RenderedBone API