Skip to content
hikari
RenderingEditorAIToolchainDocumentation
Download

© 2026 Flying Rat Studio.
All rights reserved.

Explore the engineDocumentationContributorsDownloadLicenseBack to top
Documentation / Tutorials
Browse docs
Overview
Tutorials17
OverviewPlay, Edit, and scenesFirst UI documentFirst game projectFirst entityComponent properties and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides25
OverviewWorking in the editorDevelopment guideGameplay APIActor and component lifecycleThreads, jobs, events, and routinesGameplay AI tasksGame AI: perception and movement playgroundBrain assetsBrain execution for swarm populationsLocalizationGame cursorsChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringChoosing and scaling shadowsTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / Unreal
Systems32
OverviewArchitectureApplication lifecycleFrontends and driversPlatforms and supportSession services and cross-scene stateScenes and gameplayActor communication (hi.actors)Game-facing refsSave / replication wire versionCoordinate space and camera conventionsRenderingRenderer architecture mapFrame governorPipeline compilationSkeletal animationGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitUI documentsAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)Document schemasUI and editorEditor asset hot reloadEditor Project Selector
Language reference3
OverviewAkari GPU ABIAkari language referenceKawa language reference
Engine overview
Start exploring
  • No matching sections. Try fewer words or another topic.
NavigateEnter Openesc Close
Overview
Tutorials17
OverviewPlay, Edit, and scenesFirst UI documentFirst game projectFirst entityComponent properties and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides25
OverviewWorking in the editorDevelopment guideGameplay APIActor and component lifecycleThreads, jobs, events, and routinesGameplay AI tasksGame AI: perception and movement playgroundBrain assetsBrain execution for swarm populationsLocalizationGame cursorsChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringChoosing and scaling shadowsTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / Unreal
Systems32
OverviewArchitectureApplication lifecycleFrontends and driversPlatforms and supportSession services and cross-scene stateScenes and gameplayActor communication (hi.actors)Game-facing refsSave / replication wire versionCoordinate space and camera conventionsRenderingRenderer architecture mapFrame governorPipeline compilationSkeletal animationGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitUI documentsAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)Document schemasUI and editorEditor asset hot reloadEditor Project Selector
Language reference3
OverviewAkari GPU ABIAkari language referenceKawa language reference
Engine overview
Tutorials6 min read

Tutorial: first skeletal animation

On this page
On this pagePrerequisites1. Source model2. Cook3. Archetype with animation4. Scene actor5. Play from code (optional)6. Listen for clip events (optional)7. Let a one-shot state return by itself (optional)8. Keep Walk and Run in step (optional)9. Hang a prop on a joint (optional)VerifySee also Back to top

Cook a skinned glTF into a .shinmodel with skeleton + clips, place it with hi.ComponentRender + hi.ComponentAnimation, and either play a clip by name or bind an animation graph.

Sample: hikari-games/example/assets/models/Fox.glb, graph assets/animations/locomotion-demo.animgraph.json, scene scenes/skeletal_animation.json, archetype skinned_bar.

Prerequisites

  • Product build that cooks assets (Kaji) so Shinra emits SRM1 skeleton + clip trailers.
  • A skinned glTF/GLB with exactly one skin, JOINTS_0 / WEIGHTS_0, and one or more animation clips. Every authored animation is cooked and keeps its name. A USDZ with one Skeleton, SkelBindingAPI meshes and SkelAnimation clips cooks the same way (see asset formats).

1. Source model

Sample (Fox GLB, clips Survey / Walk / Run):

PathRole
assets/models/Fox.glbSource skinned GLB
assets/models/Fox.model.jsonPlaceable model doc (Shinra companion)
assets/models/Fox.material.jsonCooked material companion
assets/animations/locomotion-demo.animgraph.jsonClip states + speed parameter

A two-bone bar (assets/models/skinned_bar.gltf, clip "Idle") is still in the shared pack if you want a minimal clip-only actor. Clip-only playback is also on scenes/brainstem_animation.json (HUD tour).

2. Cook

Kaji product / assets path (preferred):

sh
bin/kaji/kaji assets --workspace=<repo> --project=../hikari-games/example
# or full game/editor build, which cooks as part of staging
bin/kaji/kaji game --workspace=<repo> --project=../hikari-games/example --type=monolithic --config=debug

Direct Shinra (debug / CI-style check):

sh
cd src/shinra && cargo run --release -- \
  --input ../../../hikari-games/example/assets \
  --output /tmp/shinra-out \
  --target macos-metal

Cooked models/Fox.shinmodel must report has_skeletal_animation, non-zero joint_count, and clip names (sample: Survey, Walk, Run).

3. Archetype with animation

ComponentAnimation is attachable storage; the archetype must list it so scene "animation" binds a player:

zig
const hi = @import("hikari_game");

pub const SkinnedBarEntity = hi.defineActor(.{
    .archetype = "skinned_bar",
    .components = .{ hi.ComponentRender, hi.ComponentAnimation },
});

Sample: hikari-games/example/src/entities/skinned_bar_entity.zig.

4. Scene actor

Scene model placeables use a .model.json URI. Graph refs are extension-free stems.

json
{
  "id": "skel_bar",
  "archetype": "skinned_bar",
  "transform": {
    "position": [0, 0.12, 0],
    "rotation_euler": [0, 0, 0],
    "scale": [0.02, 0.02, 0.02]
  },
  "components": {
    "render": {
      "model": "asset://./models/Fox.model.json",
      "is_visible": true,
      "casts_shadow": true
    },
    "animation": {
      "graph": "asset://./animations/locomotion-demo",
      "playing": true,
      "looping": true,
      "speed": 1.0,
      "time": 0.0
    }
  }
}
  • Clip strings (when you author "clip" instead of "graph") are hashed with FNV-1a and matched to the cooked name table.
  • The scene loader derives pack dependencies from model and graph references. Configure their membership in configs/layout.json; do not author a scene packs list.

Full sample: hikari-games/example/scenes/skeletal_animation.json.

5. Play from code (optional)

Clip control (takes the actor off the graph until you hand it back):

zig
hi.animation().play(actor, "Survey");
hi.animation().setLooping(actor, true);
hi.animation().setSpeed(actor, 1.0);
// hi.animation().pause(actor);  // retain current time
// hi.animation().resumePlayback(actor); // continue current clip
// hi.animation().stop(actor);
// hi.animation().setTime(actor, 0.0);
// hi.animation().playGraph(actor); // return to the bound state machine

Requires the entity to have ComponentAnimation (same as the scene block).

Drive the sample locomotion graph with the speed parameter (0 = Survey, ~0.1 = Walk, >1 = Run):

zig
_ = hi.animation().setGraphFloat(actor, "speed", 1.5);
_ = hi.animation().playGraph(actor);

For models with several clips, enumerate the cooked names instead of duplicating an exporter-specific list in gameplay code:

zig
const animation = hi.animation();
for (0..animation.clipCount(actor)) |i| {
    const clip_name = animation.clipName(actor, @intCast(i)) orelse continue;
    // Populate a developer menu, select an initial state, etc.
    _ = clip_name;
}
if (animation.hasClip(actor, "Run")) animation.play(actor, "Run");

Kawa exposes the same controls and clip discovery through Animation:

kawa
let actor = Actors.find("hero");
let count = Animation.clip_count(actor);
let clip = Animation.clip_name(actor, 0);
Animation.play(actor, clip);
Animation.set_graph_float(actor, "speed", 1.5);
Animation.set_graph(actor, "asset://./animations/locomotion-demo");
Animation.pause(actor);
Animation.resume_playback(actor);
Animation.stop(actor); // Pauses at frame zero.

Animation.has_clip, set_time, set_speed, set_looping, is_playing, set_graph_bool, and trigger_graph mirror the Zig gameplay API. Invalid actors, missing animation components, and unknown clips return false (or nil for clip_name) instead of trapping.

The model asset viewer shows every clip in a scrollable list. Selecting a row switches the active clip and resets its timeline; play, pause, stop, loop, scrub, and the joint overlay all operate on the selection.

6. Listen for clip events (optional)

Markers placed on a clip in the graph editor (select a single-clip state → Events → Add event) reach gameplay on the frame the animation crosses them. The demo graph carries a footstep marker on Walk and Run. Declare the callback on any logic component of the archetype:

zig
pub fn onAnimationEvent(self: *@This(), actor: hi.ActorContext, event: *const hi.AnimationEvent) void {
    const id = actor.id;
    _ = self;
    if (event.is("footstep")) hi.std.log.info("{s} stepped (foot {d})", .{ hi.world().name(id), event.value });
}

Or poll from any system that runs after animation:

zig
var it = hi.animation().events(actor);
while (it.next()) |event| if (event.is("footstep")) playFootstep(event.value);
if (hi.animation().fired(actor, "impact")) applyHit();

Kawa gets the same marker as on_animation_event(name, value, time):

kawa
fn on_animation_event(name, value, time) {
    if (name == "footstep") { Audio.play("footstep"); }
}

A marker belongs to the clip, so it fires from every state and blend space that plays that clip; in a blend only the dominant clip fires, so a Walk/Run blend does not double its footsteps.

7. Let a one-shot state return by itself (optional)

Add an Attack state with looping off and draw an arrow Attack → Survey. In the arrow's card tick Wait for the clip and leave Exit at on 1: the arrow fires the moment the clip has played out, no parameter needed. Untick Can be interrupted on Survey → Attack if a second trigger during the wind-up must not restart it.

For a hit reaction from anywhere, drag from the Any State node's pin to a Hit state and give the arrow a hit trigger. It fires from every state on the layer, and a second trigger while already in Hit restarts it.

Gameplay can ask where the graph is instead of mirroring it:

zig
if (hi.animation().state(actor)) |s| {
    if (s.is("Attack") and s.progress > 0.6) self.hitbox_live = true;
}

8. Keep Walk and Run in step (optional)

Type the same Sync group name — say Locomotion — on the Walk and Run states. Both now run on one phase clock: at any moment each clip is at the same fraction of its own cycle, the blend between them advances at the blended cadence, and Walk → Run carries the phase across instead of restarting Run from its first frame. Author the clips to start on the same foot; the engine aligns the phase, not the content.

9. Hang a prop on a joint (optional)

Give the Fox a child actor (a sword, a lamp) and, with the child selected, use Attach to joint in the inspector to pick a joint of the Fox's rig. The child follows that joint as the Fox animates; its own transform is an offset from the joint. From code:

zig
_ = hi.world().attachToJoint(sword, fox, "b_Head_05");
if (hi.animation().jointNamed(fox, "b_Head_05", .world)) |head| hi.std.log.info("head at {d:.2}", .{head.position[1]});

Verify

  1. Cook succeeds and Fox.shinmodel has skeletal flag + joints + animations.
  2. Open the editor / game, load scene skeletal_animation.
  3. The Fox should pose (Survey while speed is 0). Use the visible Speed slider: above 0.1 walks, above 1 runs. The control sets the graph parameter through hi.animation().setGraphFloat.
  4. For clip-only, open scenes/brainstem_animation (Browse scenes → Tutorials → Clip Playback) or call hi.animation().play with an enumerated name.
  5. Change "clip" or call hi.animation().play with a wrong name → soft no-op (clip hash miss).

See also

  • Skeletal animation — full reference: graphs, layers, events, foot IK, root motion, readback
  • First mesh and material — asset:// render binds
  • Assets and Shinra — cook / packs
PreviousTutorial: first motionNext Tutorial: assets in Play (soft refs and hot reload)

Documentation follows the current engine checkout.

Snapshot 86f12d68Source docs/tutorials/first-skeletal-animation.md
On this pagePrerequisites1. Source model2. Cook3. Archetype with animation4. Scene actor5. Play from code (optional)6. Listen for clip events (optional)7. Let a one-shot state return by itself (optional)8. Keep Walk and Run in step (optional)9. Hang a prop on a joint (optional)VerifySee also Back to top