Skip to content
hikari
RenderingEditorAIToolchainDocumentation
GitHub
hikari

© 2026 Flying Rat Studio.
All rights reserved.

Explore the engineDocumentationContributorsLicenseBack to top
Documentation / Tutorials
Browse docs
Overview
Tutorials16
OverviewFirst game projectFirst entityAuthored user_data and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesPlay, Edit, and scenesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides16
OverviewDevelopment guideGameplay APIChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / UnrealActor and component lifecycle
Systems28
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 governorGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)UI and editorEditor asset hot reloadEditor Project Selector
Language reference2
OverviewAkari language referenceKawa language reference
Engine overview
Start exploring
  • No matching sections. Try fewer words or another topic.
NavigateEnter Openesc Close
Overview
Tutorials16
OverviewFirst game projectFirst entityAuthored user_data and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesPlay, Edit, and scenesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides16
OverviewDevelopment guideGameplay APIChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / UnrealActor and component lifecycle
Systems28
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 governorGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)UI and editorEditor asset hot reloadEditor Project Selector
Language reference2
OverviewAkari language referenceKawa language reference
Engine overview
Tutorials5 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)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.

Deep design: Skeletal animation. Sample: src/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=src/games/example
# or full game/editor build, which cooks as part of staging
bin/kaji/kaji game --workspace=<repo> --project=src/games/example --type=monolithic --config=debug

Direct Shinra (debug / CI-style check):

sh
cd src/shinra && cargo run --release -- \
  --input ../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: src/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.
  • List the pack that holds the model in scene "packs". The sample scene names shared, whose layout.json includes models/skinned_bar* and models/BrainStem* — not Fox / animations/**. Editor live-tree loads still resolve Fox; a packed game needs matching include globs.

Full sample: src/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.debug().log("{s} stepped (foot {d})", .{ 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. See animation-events.

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

  • First mesh and material — asset:// render binds
  • Assets and Shinra — cook / packs
  • Skeletal animation design — contracts
PreviousTutorial: first motionNext Tutorial: assets in Play (soft refs and hot reload)

Documentation follows the current engine checkout.

Snapshot cc148c75Source 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)VerifySee also Back to top