Skip to main content

Locomotion New

Generate consistent walking-style locomotion for a character in a specified direction with controllable style, speed, and stride count. Use locomotion when you need predictable, repeatable, looptable travel motion for gameplay, background NPCs, or any workflow where a text prompt is too open-ended to rely on the same path and style every time.

Overview

Text-to-motion is ideal when you want creative motion from a natural language description. Locomotion is different: you choose where the character should travel, how fast, how many stride pairs to use, and which style to apply, so clips stay stable and well-suited for looping and stitching in your own pipeline. Results are returned immediately, like text-to-motion, with no job polling.

Call locomotion_styles to list the style_id values accepted by create_locomotion (for example neutral_male_a).

Step-by-step tutorial

Step 1: Set up the client

Install your client library and authenticate using your API key. See the quickstart for setup instructions for each language.

Step 2: Query locomotion styles (optional)

Uthana exposes a query that returns every style_id you can pass to create_locomotion, for example neutral_male_a, aeroplane.

curl -sS -X POST "https://uthana.com/graphql" \
  -u "$API_KEY:" \
  -H "Content-Type: application/json" \
  -d '{"query":"query { locomotion_styles }"}'

Step 3: Generate locomotion

Call create_locomotion with the character to retarget to (required character_id) and the motion parameters. If you omit optional fields, the API uses these defaults:

  • strides: 4 (number of full stride pairs; allowed 1 to 5)
  • move_speed: 1.2 m/s, relative to the character's height and stride (valid range 0.25 to 8.0)
  • style_id: "neutral_male_a"
  • travel_angle: 0 degrees — 0 is North (forward), 90 is East (right), -90 is West (left), 180/-180 is South (backward); values beyond this range are reduced via modulo 360.
curl -X POST "https://uthana.com/graphql" \
  -u "$API_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation CreateLocomotion($character_id: String!, $strides: Int, $move_speed: Float, $style_id: String, $travel_angle: Float) { create_locomotion(character_id: $character_id, strides: $strides, move_speed: $move_speed, style_id: $style_id, travel_angle: $travel_angle) { motion { id name } } } }",
    "variables": {
      "character_id": "'"$CHARACTER_ID"'",
      "strides": 2,
      "move_speed": 1.3,
      "style_id": "neutral_male_a",
      "travel_angle": 0
    }
  }'

Parameters

  • character_id: String! — The character the motion is generated and retargeted for.
  • strides: Int — Count of full stride pairs (1 to 5). Default 4. Each full stride is a left+right or right+left step pair.
  • move_speed: Float — Travel speed in m/s (relative to the character; recommended roughly 0.25 to 6.0, allowed up to 8.0). Default 1.2.
  • style_id: String — Locomotion style from List locomotion styles or the optional query in Step 2 below. Resolves case-insensitively to a known Uthana style. Default neutral_male_a.
  • travel_angle: FloatTravel direction in degrees on the ground plane, normalized to -180 to 180. 0 is forward (north in Uthana's mapping), 90 is to the right, -90 to the left, 180 or -180 is backward. Default 0.

Step 4: Handle the response

The mutation returns a motion with id and name immediately, similar to text-to-motion. Use the motion id in retargeting and downloading flows. When listing motions, filter with motions(methods: ["Locomotion"]) where supported.

Step 5: Integrate with a motion controller

Locomotion clips are a good fit for motion controllers because each clip encodes a fixed travel direction (travel_angle), speed (move_speed), and style (style_id). You can build a small library of clips (cardinal directions, a few speeds, one or more styles) and select or blend between them from gameplay input instead of relying on a single open-ended prompt.

Controller checklist

  1. Generate or pick clips — Create one motion per combination you need (same character character_id), varying style_id, move_speed, and travel_angle as needed.
  2. Export for your engine — Download GLB or FBX from Download a motion using your motion id and character id.
  3. Choose root motion vs in-place — Either let the clip move the skeleton root (root motion), or download with in_place=true so the character stays under the origin while you move the character transform in code in the direction that matches travel_angle. See In-place motion.
  4. Loop and switch — Play clips with seamless looping where possible; swap or cross-fade clips when input direction or speed changes. For authored loops and progressive vs cyclical behavior after export, see Stitch and loop motions.

Load a retargeted GLB (with animation), drive it with AnimationMixer, and repeat the clip. Update the mixer each frame.

If you used in-place exports, apply world translation yourself: each frame, move your character root along the same planar direction implied by travel_angle (for example 0° forward on +Z in many setups—match your scene convention). If you keep root motion in the clip, translation usually comes from the animated root; align gameplay collision and camera with that motion.

import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";

const clock = new THREE.Clock();
let mixer: THREE.AnimationMixer | undefined;

new GLTFLoader().load("/locomotion.glb", (gltf) => {
  const root = gltf.scene;
  scene.add(root);

  const clip = gltf.animations[0];
  if (!clip) return;

  mixer = new THREE.AnimationMixer(root);
  const action = mixer.clipAction(clip);
  action.setLoop(THREE.LoopRepeat, Infinity);
  action.play();
});

function onFrame() {
  requestAnimationFrame(onFrame);
  const dt = clock.getDelta();
  mixer?.update(dt);

  // If you exported with in_place=true, move `root` (or a parent group) each frame
  // along the ground-plane direction that matches your locomotion travel_angle,
  // e.g. root.position.addScaledVector(desiredWorldDir, speed * dt);

  renderer.render(scene, camera);
}

Error handling

# Check for errors in response
if echo "$RESPONSE" | jq -e '.errors' > /dev/null; then
    echo "Error occurred:"
    echo "$RESPONSE" | jq '.errors'
fi

Next steps