# Hand Animations > Lectern's hand animation system uses the **Bedrock animation format** to drive first-person hand and item rendering. Animations can be authored in Blockbench and delivered via resource pack or sent inline from the server. --- ## Overview The hand animation system provides two levels of first-person hand control: | Effect | Packet ID | Description | |---|---|---| | **HandPose** | `hand_pose` | Static pose — holds out the hand with an item floating and spinning above it. Good for item showcases. | | **HandAnimation** | `hand_animation` | Keyframed animation — plays a full Bedrock animation on the hand and item. Supports looping, crossfading, and inline loading. | HandAnimation takes rendering priority over HandPose. If both are active, only the animation renders. --- ## Hand Pose A simple effect that positions the player's arm in a showcase pose with the item floating, spinning, and bobbing above the palm. ### Server-Side Usage ```java IScreenManager screen = ILecternAPI.get().getScreenManager(); // Show with defaults (held item, spinSpeed=2.0, bobSpeed=1.5, bobHeight=0.03) screen.showHandPose(player); // Show with a specific item and custom values screen.showHandPose(player, "minecraft:diamond_sword", 3.0f, 1.0f, 0.05f); // Stop screen.stopHandPose(player); ``` ### Packet Command ``` /packet hand_pose <player> true <itemId> <spinSpeed> <bobSpeed> <bobHeight> /packet hand_pose <player> false ``` ### Parameters | Parameter | Type | Default | Description | |---|---|---|---| | `itemId` | `String` | `""` (held item) | The item to display. Empty string uses the player's held item. | | `spinSpeed` | `float` | `2.0` | Spin speed multiplier. Higher = faster rotation. | | `bobSpeed` | `float` | `1.5` | Vertical bob speed multiplier. | | `bobHeight` | `float` | `0.03` | Vertical bob amplitude in block units. | --- ## Hand Animation A full keyframed animation system that parses Bedrock `.animation.json` files and applies them to the first-person view. Animations target three bones: | Bone Name | Controls | |---|---| | `right_arm` | The player's right arm (position, rotation). | | `left_arm` | The player's left arm (position, rotation). | | `item` | The held item (position, rotation, scale). | ### Server-Side Usage ```java IScreenManager screen = ILecternAPI.get().getScreenManager(); // Play a named animation (must be loaded via resource pack or inline) screen.playHandAnimation(player, "animation.hand.inspect"); // Play with full control screen.playHandAnimation(player, "animation.hand.inspect", "minecraft:diamond_sword", true, 1.5f); // Crossfade from the current animation to a new one screen.crossfadeHandAnimation(player, "animation.hand.wave", 0.5f, 1.0f); // Stop screen.stopHandAnimation(player); // Send an animation JSON directly (no resource pack needed) String json = Files.readString(Path.of("my_animation.json")); screen.loadInlineHandAnimation(player, "animation.hand.custom", json); ``` ### Packet Command ``` /packet hand_animation <player> PLAY <animationName> <itemId> <loop> <speed> /packet hand_animation <player> STOP /packet hand_animation <player> CROSSFADE <animationName> <blendDuration> <speed> /packet hand_animation <player> LOAD_INLINE <animationName> <json> ``` --- ## Animation JSON Format Lectern uses the standard **Bedrock animation format** (`format_version: "1.8.0"`). Animations can be created in [Blockbench](https://blockbench.net/) and exported as Bedrock animations. ### Structure ```json { "format_version": "1.8.0", "animations": { "animation.hand.inspect": { "animation_length": 3.0, "loop": true, "return_frame": 0.5, "bones": { "right_arm": { "position": { ... }, "rotation": { ... } }, "item": { "position": { ... }, "rotation": { ... }, "scale": { ... } } } } } } ``` ### Loop Modes | Value | Behavior | |---|---| | `false` | Play once and stop. The hand returns to normal after the animation finishes. | | `true` | Loop continuously until stopped. | | `"hold_on_last_frame"` | Play once and freeze on the last frame. | > When using `playHandAnimation()` with `loop=true`, the loop mode is overridden to `LOOP` regardless of what the JSON specifies. ### Return Frame When `loop` is `true`, you can set `"return_frame"` to a timestamp (in seconds) that the animation jumps back to when it reaches the end, instead of restarting from `0.0`. This lets you create animations with a one-time intro followed by a repeating loop. ```json { "animation_length": 3.0, "loop": true, "return_frame": 0.5 } ``` In this example, the animation plays `0.0s → 3.0s` on the first pass, then loops `0.5s → 3.0s` on every subsequent pass. The intro from `0.0s` to `0.5s` only plays once. If `return_frame` is omitted or `0`, the animation loops from the beginning as normal. ### Channels Each bone can have three animation channels: | Channel | Format | Description | |---|---|---| | `position` | `[x, y, z]` | Position offset in **Blockbench units** (1/16 of a block). Automatically converted to MatrixStack space. | | `rotation` | `[pitch, yaw, roll]` | Rotation in degrees. | | `scale` | `[x, y, z]` | Scale multiplier per axis. | ### Keyframe Formats Keyframes can be specified in two formats: **Simple array** — a static value with linear interpolation: ```json "position": { "0.0": [0, 0, 0], "0.5": [-4, -3.2, -12.8] } ``` **Object with interpolation control** — for catmull-rom or step curves: ```json "rotation": { "0.0": { "pre": [0, 0, 0], "post": [0, 0, 0], "lerp_mode": "catmullrom" }, "1.0": { "pre": [-50, -20, 45], "post": [-50, -20, 45], "lerp_mode": "catmullrom" } } ``` **Static channel** — a single value applied for the entire animation: ```json "scale": [0.3, 0.3, 0.3] ``` ### Interpolation Modes | Mode | Description | |---|---| | `linear` | Linear interpolation between keyframes. Default. | | `catmullrom` | Catmull-Rom spline interpolation for smooth curves through keyframes. | | `step` | Instant jump to the next keyframe value (no interpolation). | --- ## Loading Animations ### Via Resource Pack Place `.animation.json` files in the `assets/lectern/animations/` directory of a resource pack. They are loaded automatically when the resource pack is applied. ``` assets/ lectern/ animations/ inspect.animation.json wave.animation.json custom_effect.animation.json ``` Each file can contain multiple animations. All animations in all files are registered by their full name (e.g. `animation.hand.inspect`). ### Via Inline Loading Send the animation JSON directly from the server using `loadInlineHandAnimation()`. The animation is registered on the client and available immediately for playback. Inline animations persist until the player disconnects or the effect is reset. ```java // Read JSON from file, database, config, etc. String json = "{\"format_version\":\"1.8.0\",\"animations\":{...}}"; // Register it on the client screen.loadInlineHandAnimation(player, "animation.hand.custom", json); // Now it can be played screen.playHandAnimation(player, "animation.hand.custom"); ``` --- ## Crossfading Crossfading smoothly blends from the current animation's transforms into a new animation over a specified duration. The current animation's bone transforms are frozen at their current values and linearly interpolated toward the new animation's transforms. ```java // Currently playing "animation.hand.inspect" // Blend into "animation.hand.wave" over 0.5 seconds screen.crossfadeHandAnimation(player, "animation.hand.wave", 0.5f, 1.0f); ``` --- ## Example Animation An inspect animation that raises the arm then continuously spins the item, using `return_frame` to skip the intro on loop: ```json { "format_version": "1.8.0", "animations": { "animation.hand.inspect": { "animation_length": 3.0, "loop": true, "return_frame": 0.5, "bones": { "right_arm": { "rotation": { "0.0": [0, 0, 0], "0.5": [-50, -20, 45], "1.5": [-40, 10, 30], "2.5": [-50, -20, 45], "3.0": [-50, -20, 45] }, "position": { "0.0": [0, 0, 0], "0.5": [-4, -3.2, -12.8], "3.0": [-4, -3.2, -12.8] } }, "item": { "position": { "0.0": [0, 0, 0], "0.5": [-0.8, 8, -2.4], "3.0": [-0.8, 8, -2.4] }, "rotation": { "0.0": [0, 0, 0], "1.5": [0, 180, 0], "3.0": [0, 360, 0] }, "scale": [0.3, 0.3, 0.3] } } } } } ``` This animation: 1. **0.0s–0.5s** — Intro: arm raises from default position to the showcase pose (plays once) 2. **0.5s–3.0s** — Loop: arm holds position while the item spins a full 360° 3. **3.0s** — Jumps back to `0.5s` and repeats the spin --- ## Blockbench Workflow 1. Open [Blockbench](https://blockbench.net/) and create a new **Bedrock Entity** project 2. Create bones named `right_arm`, `left_arm`, and/or `item` 3. Switch to the **Animate** tab and create a new animation 4. Set keyframes for position, rotation, and scale on each bone 5. Configure loop mode and animation length 6. Export as **Bedrock Animation** (`.animation.json`) 7. Place the file in `assets/lectern/animations/` or send it inline via the API > **Coordinate note:** Blockbench positions are in 1/16 block units (pixels). Lectern automatically divides by 16 when applying to the MatrixStack. --- ## Related Pages - [[IScreenManager]] — API methods for hand pose and hand animation - [[ILecternAPI]] — Main API entry point