Locomotion | Uthana API Docs
Generate Consistent Walking-Style Locomotion
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.
Example Queries
- Shell
curl -sS -X POST "https://uthana.com/graphql" \
-u "$API_KEY:" \
-H "Content-Type: application/json" \
-d '{"query":"query { locomotion_styles }"}'
- Python
async def main():
styles = await client.motions.list_locomotion_styles()
print(styles)
asyncio.run(main())
- TypeScript
const styles = await client.motions.listLocomotionStyles();
console.log(styles);
- React
import { useUthanaLocomotionStyles } from "@uthana/react";
function StyleList() {
const { styles } = useUthanaLocomotionStyles();
return <ul>{styles?.map((s) => <li key={s}>{s}</li>)}</ul>;
}
- C#
var styleBody = @"{"query":"query { locomotion_styles }"}";
var styleContent = new StringContent(styleBody, Encoding.UTF8, "application/json");
_httpClient.DefaultRequestHeaders.Authorization = new(
"Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{_apiKey}:"))
);
var styleResp = await _httpClient.PostAsync(ApiUrl, styleContent);
var styleJson = await styleResp.Content.ReadAsStringAsync();
// Parse locomotion_styles from `styleJson` as needed
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; allowed1to5)move_speed:1.2m/s, relative to the character's height and stride (valid range0.25to8.0)style_id: "neutral_male_a"travel_angle:0degrees —0is North (forward),90is East (right),-90is West (left),180/-180is South (backward); values beyond this range are reduced via modulo 360.
Example Queries
- Shell
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
}
}'
- Python
async def main():
result = await client.motions.create_locomotion(
CHARACTER_ID,
strides=2,
move_speed=1.3,
style_id="neutral_male_a",
travel_angle=0,
)
print(f"Created motion: {result.motion_id}")
asyncio.run(main())
- TypeScript
const result = await client.motions.createLocomotion(CHARACTER_ID, {
strides: 2,
move_speed: 1.3,
style_id: "neutral_male_a",
travel_angle: 0,
});
console.log(`Created motion: ${result.motion_id}`);
- React
import { useUthanaCreateLocomotion, useUthanaLocomotionStyles } from "@uthana/react";
function LocomotionPanel({ characterId }: { characterId: string }) {
const { styles } = useUthanaLocomotionStyles();
const createLoco = useUthanaCreateLocomotion();
return (
<div>
<p>Styles: {styles?.join(", ")}</p>
<button
onClick={() =>
createLoco.mutate({
character_id: characterId,
strides: 2,
move_speed: 1.3,
style_id: "neutral_male_a",
travel_angle: 0,
})
}
disabled={createLoco.isPending}
>
{createLoco.isPending ? "Generating..." : "Generate locomotion"}
</button>
{createLoco.data && <p>Motion ID: {createLoco.data.motion_id}</p>}
</div>
);
}
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
- Generate or pick clips — Create one motion per combination you need (same character
character_id), varyingstyle_id,move_speed, andtravel_angleas needed. - Export for your engine — Download GLB or FBX from Download a motion using your motion id and character id.
- Choose root motion vs in-place — Either let the clip move the skeleton root ( root motion), or download with
in_place=trueso the character stays under the origin while you move the character transform in code in the direction that matchestravel_angle. See In-place motion. - Loop and switch — Play clips with seamless looping where possible; swap or cross-fade clips when input direction or speed changes.
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.move_speed:Float— Travel speed in m/s. Recommended roughly 0.25 to 6.0, allowed up to 8.0. Default 1.2.style_id:String— Locomotion style. Resolves case-insensitively to a known Uthana style. Defaultneutral_male_a.travel_angle:Float— Travel direction in degrees on the ground plane, normalized to -180 to 180. Default 0.
Error handling
- Shell
if echo "$RESPONSE" | jq -e '.errors' > /dev/null; then
echo "Error occurred:";
echo "$RESPONSE" | jq '.errors';
fi
- Python
from uthana import UthanaError
async def main():
try:
result = await client.motions.create_locomotion(CHARACTER_ID, strides=2)
except UthanaError as e:
print(e.status_code, e.api_message)
asyncio.run(main())
Next steps
- See Text to motion for open-ended, prompt-driven generation.
- See Download a motion to export assets.
- Explore Retargeting for applying motions to other characters.