Table of Contents
- Animation Description Language (ADL)
Animation Description Language (ADL)
Audience: Engine maintainers and module developers creating animated humanoid characters.
The Animation Description Language (ADL) is a declarative system for defining skeletal animations for humanoid actors. It uses .properties files to define body parts, skeletons, and frame-by-frame transformations, which are then assembled into animated spritesheets at runtime.
Overview
ADL provides:
- Skeletal animation — Define joints and transform them per-frame
- Modular body parts — Separate images for head, torso, limbs, etc.
- Animation sequences — Run cycles, idle animations, etc.
- Equipment layering — Headgear, held items, armour overlays
- Runtime assembly — Build final spritesheet from components
ADL File Format
ADL uses Java .properties format with special conventions.
File Structure
SPRITESHEET=mods/basegame/sprites/fofu/fofu_
EXTENSION=.tga
CONFIG=SIZE 48,56;ORIGINX 29
BODYPARTS=HEADGEAR 11,11;\
HEAD 11,11;\
ARM_REST_RIGHT 4,2;\
TORSO 10,4
SKELETON_STAND=HEADGEAR 0,32;HEAD 0,32;\
ARM_REST_RIGHT -7,23;\
TORSO 0,22
ANIM_RUN=DELAY 0.15;ROW 2;SKELETON SKELETON_STAND
ANIM_RUN_1=LEG_REST_RIGHT 1,1;LEG_REST_LEFT -1,0
ANIM_RUN_2=ALL 0,1;LEG_REST_RIGHT 0,-1;LEG_REST_LEFT 0,1
Reserved Keywords
Global Properties:
SPRITESHEET— Base path for body part images (required)EXTENSION— File extension for body part images (required)CONFIG— Frame size and origin point (required)BODYPARTS— List of body parts with joint positions (required)
Skeleton Definitions:
SKELETON_*— Defines a skeleton (e.g.,SKELETON_STAND,SKELETON_CROUCH)
Animation Definitions:
ANIM_*— Defines an animation (e.g.,ANIM_RUN,ANIM_IDLE)ANIM_*_N— Defines frame N of an animation (e.g.,ANIM_RUN_1,ANIM_RUN_2)
Special Body Parts:
HEADGEAR— Equipped helmet/hatHELD_ITEM— Item in handBOOT_L,BOOT_R— Left/right bootsGAUNTLET_L,GAUNTLET_R— Left/right gauntletsARMOUR_*— Armour layers (e.g.,ARMOUR_0,ARMOUR_1)
Property Types
SPRITESHEET and EXTENSION
SPRITESHEET=mods/basegame/sprites/fofu/fofu_
EXTENSION=.tga
Body part files are constructed as: SPRITESHEET + bodypart_name + EXTENSION
Example:
SPRITESHEET=mods/basegame/sprites/fofu/fofu_- Body part:
HEAD - Result:
mods/basegame/sprites/fofu/fofu_head.tga
CONFIG
CONFIG=SIZE 48,56;ORIGINX 29
Parameters:
SIZE w,h— Frame dimensions (width, height) in pixels (required)ORIGINX x— X-coordinate of origin point (required)
Origin is always (originX, 0) — the top-centre anchor point for the character.
Frame dimensions include extra headroom:
frameWidth = configWidth + 32 // EXTRA_HEADROOM_X = 32
frameHeight = configHeight + 16 // EXTRA_HEADROOM_Y = 16
BODYPARTS
BODYPARTS=HEAD 11,11;\
ARM_REST_RIGHT 4,2;\
LEG_REST_LEFT 4,7;\
TORSO 10,4;\
HELD_ITEM 0,0
Defines the list of body parts and their joint positions (anchor points within each sprite).
Format: BODYPART_NAME jointX,jointY
Joint Position:
- Coordinates are relative to the body part sprite's top-left corner
- Joint is where this body part connects to the skeleton
- Example:
HEAD 11,11— Head sprite's joint is 11 pixels right, 11 pixels down from top-left
Paint Order: Body parts are painted in reverse order — last in the list paints first (background), first in the list paints last (foreground).
# Paint order: TORSO (back) → LEG → ARM → HEAD (front)
BODYPARTS=HEAD 8,7;\
ARM_REST_RIGHT 3,8;\
LEG_REST_RIGHT 3,7;\
TORSO 9,4
SKELETON Definitions
SKELETON_STAND=HEADGEAR 0,32;\
HEAD 0,32;\
ARM_REST_RIGHT -7,23;\
TORSO 0,22;\
LEG_REST_RIGHT -2,7
Defines joint positions for a skeleton pose.
Format: SKELETON_NAME=JOINT_NAME offsetX,offsetY;...
Joint Offsets:
- Coordinates are relative to the character's origin
(originX, 0) - Positive X → right, Positive Y → down
- Example:
HEAD 0,32— Head joint is 0 pixels right, 32 pixels down from origin - Example:
ARM_REST_RIGHT -7,23— Right arm joint is 7 pixels left, 23 pixels down
Multiple Skeletons:
SKELETON_STAND=HEAD 0,32;TORSO 0,22;LEG_LEFT 2,7
SKELETON_CROUCH=HEAD 0,28;TORSO 0,20;LEG_LEFT 3,5
SKELETON_JUMP=HEAD 0,30;TORSO 0,21;LEG_LEFT 1,8
ANIM Definitions
ANIM_RUN=DELAY 0.15;ROW 2;SKELETON SKELETON_STAND
Defines an animation sequence.
Parameters:
DELAY seconds— Time between frames (float; actors may override delays)ROW row_number— Row in the output spritesheet (starts at 1)SKELETON skeleton_name— Which skeleton this animation uses
Frame Count:
Frame count is determined by the highest ANIM_*_N suffix:
ANIM_RUN=DELAY 0.15;ROW 2;SKELETON SKELETON_STAND
ANIM_RUN_1=...
ANIM_RUN_2=...
ANIM_RUN_3=...
ANIM_RUN_4=...
# This animation has 4 frames
ANIM Frame Definitions
ANIM_RUN_1=LEG_REST_RIGHT 1,1;LEG_REST_LEFT -1,0
ANIM_RUN_2=ALL 0,1;LEG_REST_RIGHT 0,-1;LEG_REST_LEFT 0,1
Defines per-joint transformations for a specific frame.
Format: ANIM_NAME_N=JOINT translateX,translateY;...
Transformations:
JOINT offsetX,offsetY— Translate joint by (offsetX, offsetY) pixelsALL offsetX,offsetY— Translate ALL joints by (offsetX, offsetY) pixels
Empty Frame:
ANIM_IDLE_1=
# Frame 1: No transformations (use skeleton as-is)
Overlapping Transforms:
ANIM_RUN_2=ALL 0,1;LEG_REST_RIGHT 0,-1
# 1. Move everything down 1 pixel
# 2. Then move right leg up 1 pixel (net: 0 offset for right leg)
Complete Example
# File paths
SPRITESHEET=mods/basegame/sprites/fofu/fofu_
EXTENSION=.tga
# Frame configuration
CONFIG=SIZE 48,56;ORIGINX 29
# Body parts with joint positions
BODYPARTS=HEADGEAR 11,11;\
HEAD 11,11;\
ARM_REST_RIGHT 4,2;\
ARM_REST_LEFT 4,2;\
LEG_REST_RIGHT 4,7;\
LEG_REST_LEFT 4,7;\
TORSO 10,4;\
TAIL_0 20,1;\
HELD_ITEM 0,0
# Skeleton: standing pose (paint order: top to bottom)
SKELETON_STAND=HEADGEAR 0,32;\
HEAD 0,32;\
ARM_REST_RIGHT -7,23;\
ARM_REST_LEFT 5,24;\
TORSO 0,22;\
LEG_REST_RIGHT -2,7;\
LEG_REST_LEFT 2,7;\
TAIL_0 0,13;\
HELD_ITEM -6,11
# Animation: running (4 frames, 0.15s per frame)
ANIM_RUN=DELAY 0.15;ROW 2;SKELETON SKELETON_STAND
ANIM_RUN_1=LEG_REST_RIGHT 1,1;LEG_REST_LEFT -1,0
ANIM_RUN_2=ALL 0,1;LEG_REST_RIGHT 0,-1;LEG_REST_LEFT 0,1
ANIM_RUN_3=LEG_REST_RIGHT -1,0;LEG_REST_LEFT 1,1
ANIM_RUN_4=ALL 0,1;LEG_REST_RIGHT 0,1;LEG_REST_LEFT 0,-1
# Animation: idle (2 frames, 2s per frame)
ANIM_IDLE=DELAY 2;ROW 1;SKELETON SKELETON_STAND
ANIM_IDLE_1=
ANIM_IDLE_2=TORSO 0,-1;HEAD 0,-1;HELD_ITEM 0,-1;\
ARM_REST_LEFT 0,-1;ARM_REST_RIGHT 0,-1;\
HEADGEAR 0,-1
ADProperties Class
The Kotlin class that parses ADL files.
Loading ADL
val adl = ADProperties(gdxFile)
Constructors:
constructor(gdxFile: FileHandle)
constructor(reader: Reader)
constructor(inputStream: InputStream)
Properties
class ADProperties {
// File information
lateinit var baseFilename: String // From SPRITESHEET
lateinit var extension: String // From EXTENSION
// Frame configuration
var frameWidth: Int // From CONFIG SIZE + headroom
var frameHeight: Int // From CONFIG SIZE + headroom
var originX: Int // From CONFIG ORIGINX
// Spritesheet dimensions
var rows: Int // Max animation row
var cols: Int // Max frame count
// Body parts
lateinit var bodyparts: List<String>
lateinit var bodypartFiles: List<String>
val bodypartJoints: HashMap<String, ADPropertyObject.Vector2i>
// Animation data
internal lateinit var skeletons: HashMap<String, Skeleton>
internal lateinit var animations: HashMap<String, Animation>
internal lateinit var transforms: HashMap<String, List<Transform>>
}
Data Classes
// Joint in a skeleton
internal data class Joint(
val name: String,
val position: ADPropertyObject.Vector2i
)
// Skeleton pose
internal data class Skeleton(
val name: String,
val joints: List<Joint>
)
// Animation sequence
internal data class Animation(
val name: String,
val delay: Float, // Seconds per frame
val row: Int, // Row in spritesheet
val frames: Int, // Frame count
val skeleton: Skeleton
)
// Per-frame joint transformation
internal data class Transform(
val joint: Joint,
val translate: ADPropertyObject.Vector2i
)
Vector2i
data class Vector2i(var x: Int, var y: Int) {
operator fun plus(other: Vector2i) = Vector2i(x + other.x, y + other.y)
operator fun minus(other: Vector2i) = Vector2i(x - other.x, y - other.y)
fun invertY() = Vector2i(x, -y)
fun invertX() = Vector2i(-x, y)
fun invertXY() = Vector2i(-x, -y)
}
Rendering Process
ADL animations are rendered on-the-fly each frame by AssembledSpriteAnimation.renderThisAnimation().
On-the-Fly Rendering
class AssembledSpriteAnimation(
val adp: ADProperties,
parentActor: ActorWithBody,
val disk: SimpleFileSystem?,
val isGlow: Boolean,
val isEmissive: Boolean
) : SpriteAnimation(parentActor) {
// Body part textures cached in memory
@Transient private val res = HashMap<String, TextureRegion?>()
var currentAnimation = "" // e.g., "ANIM_IDLE", "ANIM_RUN"
var currentFrame = 0 // Current frame index (zero-based)
fun renderThisAnimation(
batch: SpriteBatch,
posX: Float,
posY: Float,
scale: Float,
animName: String, // e.g., "ANIM_RUN_2"
mode: Int = 0
)
}
Rendering Steps (per frame)
- Load body part textures — Cache
TextureRegionfor each body part - Get animation data — Retrieve skeleton and transforms for current frame
- Calculate positions — For each body part:
val skeleton = animation.skeleton.joints.reversed() val transforms = adp.getTransform("ANIM_RUN_2") val bodypartOrigins = adp.bodypartJoints AssembleFrameBase.makeTransformList(skeleton, transforms).forEach { (name, bodypartPos) -> // Calculate final position val drawPos = adp.origin + bodypartPos - bodypartOrigins[name] // Draw body part texture at calculated position batch.draw(texture, drawPos.x * scale, drawPos.y * scale) } - Draw equipment — Render held items and armour at joint positions
No pre-assembly required — Body parts are positioned and drawn directly each frame.
Example Rendering: ANIM_RUN_2
ADL Definition:
SKELETON_STAND=HEAD 0,32;TORSO 0,22;LEG_RIGHT -2,7
ANIM_RUN_2=ALL 0,1;LEG_RIGHT 0,-1
BODYPARTS=LEG_REST_RIGHT 4,7
CONFIG=SIZE 48,56;ORIGINX 29
Rendering LEG_RIGHT (per frame):
- Origin:
(29, 0)(fromORIGINX 29) - Skeleton pose:
LEG_RIGHT -2,7→ joint offset from origin =(-2, 7) - ALL transform:
0,1→ shift all joints by(0, 1) - LEG_RIGHT transform:
0,-1→ shift this joint by(0, -1) - Final joint offset:
(-2, 7) + (0, 1) + (0, -1)=(-2, 7) - Joint in world:
(29, 0) + (-2, 7)=(27, 7) - Body part anchor:
LEG_REST_RIGHT 4,7(from BODYPARTS) - Draw position:
(27, 7) - (4, 7)=(23, 0)
Body part sprite is drawn at (23, 0) relative to character origin, with its anchor point at the skeleton joint (27, 7).
Practical Usage
Creating a New Character
-
Draw body parts as separate images:
character_head.tgacharacter_torso.tgacharacter_arm_rest_left.tgacharacter_arm_rest_right.tgacharacter_leg_rest_left.tgacharacter_leg_rest_right.tga
-
Mark joint positions on each sprite (where it connects)
-
Write ADL file:
SPRITESHEET=mods/mymod/sprites/character/character_
EXTENSION=.tga
CONFIG=SIZE 48,56;ORIGINX 24
BODYPARTS=HEAD 10,8;\
ARM_REST_RIGHT 5,3;\
ARM_REST_LEFT 5,3;\
TORSO 12,6;\
LEG_REST_RIGHT 4,8;\
LEG_REST_LEFT 4,8
SKELETON_STAND=HEAD 0,30;\
ARM_REST_RIGHT -8,24;\
ARM_REST_LEFT 8,24;\
TORSO 0,22;\
LEG_REST_RIGHT -3,8;\
LEG_REST_LEFT 3,8
ANIM_IDLE=DELAY 1;ROW 1;SKELETON SKELETON_STAND
ANIM_IDLE_1=
- Load in code:
val adl = ADProperties(ModMgr.getGdxFile("mymod", "sprites/character.properties"))
actor.sprite = AssembledSpriteAnimation(adl, actor, isGlow = false, isEmissive = false)
actor.sprite.currentAnimation = "ANIM_IDLE"
Adding New Animations
# Walk cycle (8 frames)
ANIM_WALK=DELAY 0.1;ROW 3;SKELETON SKELETON_STAND
ANIM_WALK_1=LEG_REST_RIGHT 0,1;ARM_REST_LEFT 0,1
ANIM_WALK_2=LEG_REST_RIGHT 1,0;ARM_REST_LEFT 1,-1
ANIM_WALK_3=LEG_REST_RIGHT 1,-1;ARM_REST_LEFT 2,-2
ANIM_WALK_4=ALL 0,1;LEG_REST_RIGHT 0,-1;ARM_REST_LEFT 0,1
ANIM_WALK_5=LEG_REST_LEFT 0,1;ARM_REST_RIGHT 0,1
ANIM_WALK_6=LEG_REST_LEFT 1,0;ARM_REST_RIGHT 1,-1
ANIM_WALK_7=LEG_REST_LEFT 1,-1;ARM_REST_RIGHT 2,-2
ANIM_WALK_8=ALL 0,1;LEG_REST_LEFT 0,-1;ARM_REST_RIGHT 0,1
Equipment Layering
Use special body part names for equipment:
BODYPARTS=HEADGEAR 11,11;\ # Helmet/hat slot
HELD_ITEM 0,0;\ # Item in hand
GAUNTLET_L 3,3;\ # Left glove
GAUNTLET_R 3,3;\ # Right glove
BOOT_L 4,2;\ # Left boot
BOOT_R 4,2;\ # Right boot
ARMOUR_0 10,4;\ # Armour layer 0
ARMOUR_1 10,4 # Armour layer 1
These slots are rendered dynamically from actor inventory:
// In AssembledSpriteAnimation.renderThisAnimation()
if (name in jointNameToEquipPos) {
val item = (parentActor as? Pocketed)?.inventory?.itemEquipped?.get(jointNameToEquipPos[name])
fetchItemImage(mode, item)?.let { image ->
// Draw equipped item at joint position
batch.draw(image, drawPos.x, drawPos.y)
}
}
Equipment is rendered automatically when actor has items equipped.
Best Practises
- Use consistent joint positions — Same body part across animations should have same joint
- Paint order matters — List body parts background-to-foreground
- Use ALL for body movement — Move entire character up/down with
ALL - Keep frame delays consistent — Use same delay for similar animations (e.g., all walks 0.1s)
- Test with origin marker — Verify origin is at character's centre-top
- Use skeletons for poses — Define
SKELETON_CROUCH,SKELETON_JUMPfor clarity - Name body parts clearly — Use
_LEFT/_RIGHT,_REST/_ACTIVEconventions - Add headroom — ADProperties adds 32×16 pixels automatically; don't pre-add
Common Pitfalls
- Wrong paint order — Arm painting behind torso instead of in front
- Inconsistent joint positions — Head joint moves between body parts
- Forgetting frame numbers —
ANIM_RUN_1,ANIM_RUN_2, notANIM_RUN_0 - Missing ALL transform — Forgot to move entire body up/down
- Wrong coordinate space — Mixing up joint offsets vs. world positions
- Overlapping transforms —
ALLand individual transforms don't add correctly - Origin misalignment — Character appears to "slide" when moving
- Negative delays — Invalid delay value
Advanced Techniques
Layered Animations
# Base layer (body)
SKELETON_BASE=TORSO 0,22;LEG_LEFT 2,7;LEG_RIGHT -2,7
# Upper body layer (can animate separately)
SKELETON_UPPER=HEAD 0,32;ARM_LEFT 6,24;ARM_RIGHT -6,24
# Combine in animations
ANIM_WALK=DELAY 0.1;ROW 2;SKELETON SKELETON_BASE
ANIM_ATTACK=DELAY 0.05;ROW 3;SKELETON SKELETON_UPPER
Dynamic Body Part Swapping
Body parts are loaded on construction and cached:
class AssembledSpriteAnimation {
@Transient private val res = HashMap<String, TextureRegion?>()
init {
// Load all body parts from ADL
adp.bodyparts.forEach {
res[it] = getPartTexture(fileGetter, it)
}
}
}
To swap body parts dynamically:
// Load base character
val sprite = AssembledSpriteAnimation(adl, actor, false, false)
// Replace body part texture (requires modifying internal res map)
val helmetTexture = loadHelmet("iron_helmet.tga")
// Note: res is private; body part swapping typically uses equipment slots instead
// For equipment, use HELD_ITEM/HEADGEAR slots which render from inventory
actor.inventory.itemEquipped[GameItem.EquipPosition.HEAD] = ItemCodex["item:helmet_iron"]
// Equipment renders automatically in renderThisAnimation()
Mirror Animations
AssembledSpriteAnimation supports horizontal and vertical flipping:
class AssembledSpriteAnimation {
var flipHorizontal = false
var flipVertical = false
}
// Mirror character for right-facing
sprite.flipHorizontal = true
// In renderThisAnimation(), flipping is applied:
fun renderThisAnimation(...) {
if (flipHorizontal) bodypartPos = bodypartPos.invertX()
if (flipVertical) bodypartPos = bodypartPos.invertY()
// Draw with negative width/height for flipping
if (flipHorizontal && flipVertical)
batch.draw(image, x, y, -w, -h)
else if (flipHorizontal)
batch.draw(image, x, y, -w, h)
// ...
}