Skip to main content
Version: 26.1+

Animations

Palladium uses PlayerAnimationLibrary under the hood to animate the player model. Animations are defined in the GeckoLib animation format, which can be authored in Blockbench using the GeckoLib plugin and its GeckoLib Animated Model model type.

Rather than playing a single static animation, most powers need to react to what the player is doing: play an idle pose while standing still, switch to a running cycle when sprinting, or trigger an attack animation when an ability fires. That is what animation controllers are for.

An animation controller is a state machine defined in a JSON file. You declare a set of named states, each playing one animation. Then you define transitions between them: conditions that are checked every tick, causing the controller to switch to a different state when they become true. At any given moment the controller is in exactly one state, playing its assigned animation.

For example, a simple movement controller might have three states: idle, running, and jumping. From idle it transitions to running when the player starts sprinting, and to jumping when they leave the ground. Each of those states can then transition back to idle once the condition no longer holds.

Animation controllers are assigned to a power per layer (movement, idle, or special). Each layer runs independently, so a power can have a movement controller and a special controller active at the same time without them interfering.


Animations​

Animation files are placed at:

assets/<namespace>/player_animations/<name>.animation.json

The resource ID of an individual animation is determined by the animation's name inside the file itself, for example an animation named hero_idle in the mypack namespace is referenced as mypack:hero_idle.

Within an animation controller you reference an animation by that ID:

"animation": "mypack:hero_idle"

You can also define named shortcuts in the animations map (see Animation References below) and reference them with a # prefix:

"animation": "#idle"

Animation Controllers​

Animation controller files are client-side content placed at:

assets/<namespace>/palladium/animation_controllers/<id>.json

For example, assets/mypack/palladium/animation_controllers/hero_movement.json is referenced as mypack:hero_movement.

Controller Types​

Controllers come in types, chosen with the type field. Without type, the default state machine is used.

TypeDescription
palladium:defaultState machine with states and transitions. Full control, used when type is omitted.
palladium:triggeredOne-shot animations fired by conditions, no states to write. See Triggered.

Fields shared by all types:

FieldTypeDefaultDescription
typeIdentifierdefaultThe controller type.
parentIdentifierInherit from another controller of the same type. See Inheritance.
animationsMap<String, Identifier>{}Named animation shortcuts. Keys are used as #name references. A list of IDs picks a random one each time.
blend_transitionInteger (ticks)5How many ticks to blend between animations when switching. 0 switches instantly.

Default (state machine)​

FieldTypeDefaultDescription
initial_stateStringThe state the controller starts in.
statesMap<String, State>{}The states of this controller. Keys are state names.

States​

Each entry in states defines one step in the state machine.

FieldTypeDefaultDescription
animationStringpalladium:emptyThe animation to play while in this state. Accepts a full ID or a #key reference.
first_person_animationStringThe animation to play instead while the player is in first-person view. See First-Person Animations.
blend_transitionInteger (ticks)Overrides the controller's blend_transition when switching into this state.
transitionsMap<String, Condition>{}Conditions that move the controller to another state. Keys are the target state names.

Transitions are evaluated in definition order every tick. The first condition that passes triggers an immediate state change.

Minimal example​

assets/mypack/palladium/animation_controllers/hero_movement.json
{
"initial_state": "idle",
"blend_transition": 10,
"states": {
"idle": {
"animation": "mypack:hero_idle",
"transitions": {
"running": "query.is_sprinting() && query.is_on_ground()"
}
},
"running": {
"animation": "mypack:hero_run",
"transitions": {
"idle": "query.is_sprinting() == false || query.is_on_ground() == false"
}
}
}
}

Here is what this controller does, step by step:

  • initial_state: "idle": when the controller first becomes active, it starts in the idle state.
  • blend_transition: 10: when switching between states, the two animations are blended together over 10 ticks (half a second) so the transition looks smooth instead of snapping.
  • idle state: plays mypack:hero_idle. Every tick it checks whether the player is sprinting and on the ground. If both are true, the controller moves to the running state.
  • running state: plays mypack:hero_run. Every tick it checks whether the player has stopped sprinting or has left the ground. If either is true, the controller moves back to idle.

The result is a controller that smoothly switches between an idle and a running animation based on what the player is doing.

Animation References​

The top-level animations map lets you give short names to animation IDs so you can reuse or override them (especially useful with inheritance):

{
"animations": {
"idle": "mypack:hero_idle",
"run": "mypack:hero_run"
},
"initial_state": "idle",
"states": {
"idle": {
"animation": "#idle",
"transitions": { "running": "query.is_sprinting()" }
},
"running": {
"animation": "#run",
"transitions": { "idle": "query.is_sprinting() == false" }
}
}
}

Triggered​

"type": "palladium:triggered" is for one-shot animations: give it animations and the conditions that fire them. Each trigger plays its animation once when its condition becomes true, then waits until the condition is false again before it can fire again. Looping animations are not suitable here, since the controller waits for the animation to finish.

FieldTypeDefaultDescription
triggersMap<Animation, Condition>{}Keys are animation IDs or #name references, values are conditions. Earlier entries win when several are true.
assets/mypack/palladium/animation_controllers/hero_special.json
{
"type": "palladium:triggered",
"animations": {
"attack": "mypack:hero_attack"
},
"triggers": {
"#attack": {
"type": "palladium:ability_enabled",
"ability": "mypack:hero#attack"
},
"mypack:hero_land": "query.is_on_ground() && palladium.fall_distance() > 3"
}
}

Internally this is the same state machine as the default type (idle → playing → finished → idle), so everything that applies to transitions applies here too.

Inheritance​

Use parent to inherit from another controller of the same type. The child's own values override anything from the parent, which is useful for creating variants without duplicating the full definition: the default type merges states, the triggered type merges triggers, both merge animations.

assets/mypack/palladium/animation_controllers/hero_elite_movement.json
{
"parent": "mypack:hero_movement",
"animations": {
"run": "mypack:hero_elite_run"
}
}

Here everything is inherited from mypack:hero_movement, but the #run animation reference is swapped out for the elite variant.


Transitions & Conditions​

Transitions are a map where each key is a target state name and the value is a Condition. Two formats are supported:

MoLang (string)​

A plain string is treated as a MoLang expression. The expression must evaluate to a truthy number (!= 0):

"transitions": {
"running": "query.is_sprinting() && query.is_on_ground()",
"jumping": "query.is_on_ground() == false"
}

Useful MoLang queries for animation controllers:

QueryDescription
query.is_on_ground()1.0 when the player is standing on the ground.
query.is_sprinting()1.0 when the player is sprinting.
query.is_moving()1.0 when the player has non-zero horizontal velocity.
palladium.any_animation_finished()1.0 on the tick the current animation finishes. Use to chain one-shot animations.
palladium.flight_speed_forward()Forward flight speed (useful in flight controllers).
palladium.flight_speed_sideways()Lateral flight speed.
palladium.flight_propulsion_scale()Propulsion scale of the current flight type (e.g. boost).
math.abs(x)Absolute value, handy for checking sideways speed.

All available queries are listed on the MoLang page.

Palladium Condition​

Any Palladium condition type works as well. Use a JSON object with a "type" field:

"transitions": {
"attacking": {
"type": "palladium:ability_enabled",
"ability": "mypack:hero#attack"
}
}

AND / list conditions​

You can pass a list to require multiple conditions simultaneously:

"transitions": {
"sprint_jump": [
"query.is_sprinting()",
"query.is_on_ground() == false"
]
}

Adding Controllers to Powers​

Animation controllers are assigned to powers through power renderers. Inside a power renderer file, use the animation_controller field to map one or more layers to animation controller IDs:

assets/mypack/palladium/power_renderers/hero.json
{
"animation_controller": {
"movement": "mypack:hero_movement",
"special": "mypack:hero_special"
}
}

The power renderer file must have the same resource ID as the power it belongs to. For example, the power at data/mypack/palladium/power/hero.json is paired with the renderer at assets/mypack/palladium/power_renderers/hero.json. See Power Renderers for more details.

Only the layers you specify are affected. Other layers continue to run whatever the next active power (or the defaults) provide.

If multiple active powers define a controller for the same layer, the controller from the more dominant power takes effect.

Animation Layers​

Palladium registers three independent animation layers on the player model that powers can use. Each layer runs its own controller and they stack on top of each other by priority; higher-priority layers win when bones conflict.

LayerKeyPriorityPurpose
Movementmovement500Locomotion animations (walking, running, jumping).
Idleidle1000Ambient standing / idle animations.
Specialspecial2000Ability-triggered animations (attacks, transformations, etc.).

First-Person Animations​

In first-person view, the player only sees the vanilla first-person arm. Animations don't affect it, unless the current state of a controller has a first_person_animation. While the player is in first-person view, that animation plays instead of the state's animation, and its bones are applied to the first-person arm, including all render layer parts attached to it.

"holding": {
"animation": "mypack:hold_item",
"first_person_animation": "mypack:hold_item_first_person"
}

A first-person animation is relative to the vanilla first-person arm, not to the body: the rotation and position of the arm are added on top of the pose it has when the hand is empty, the same way the attack swing is applied on top of it. A rotation of 0 keeps the arm exactly where vanilla puts it, so animations that start or end there blend seamlessly with the vanilla arm. Only the bones of the arms and their children are used. When multiple layers play a first-person animation, the one with the highest priority is used.


Examples​

One-shot ability animation​

A common pattern is triggering a single-play animation when an ability is active, then returning to the default state once it finishes. The idle state waits until the ability is enabled, then transitions to attack. Once the attack animation plays through, palladium.any_animation_finished() fires and the controller returns to idle.

{
"initial_state": "idle",
"states": {
"idle": {
"transitions": {
"attack": {
"type": "palladium:ability_enabled",
"ability": "mypack:hero#attack"
}
}
},
"attack": {
"animation": "mypack:hero_attack",
"transitions": {
"idle": "palladium.any_animation_finished()"
}
}
}
}