Open-world games and crowd simulations often have a problem that’s easy to overlook until a reviewer points it out: all the NPCs look structurally identical. Same shoulder width. Same hip proportion. Same leg length. Scaled versions of one base mesh. It reads as a crowd, not a population.
Generating statistically realistic body diversity doesn’t require 3D scanning or photogrammetry. Height and weight — two numeric inputs — produce enough structural variation to make a crowd feel like it was drawn from a real population. DimensionsPot returns the full dimensional profile for any combination, and its stateless architecture means batch generation is just a collection of parallel HTTP requests.
This guide covers the generation pipeline: designing archetypes, generating parameter distributions, making batch API calls with proper rate limiting, mapping the returned dimensions to Unity’s humanoid rig, and storing results as game assets.
The generation pipeline
Archetype definition (region, gender, height/weight distribution, build type)
↓
Parameter sampling (Gaussian random per archetype)
↓
Batch API calls (async, batched to respect rate limits)
↓
Dimension extraction (biacromial_breadth, hip_circumference, inseam_length, etc.)
↓
Bone scale calculation (dimension / reference mesh dimension)
↓
Stored as ScriptableObjects or JSON per NPC prefab
You run this pipeline offline — during development or level design — not at runtime. The generated dimension sets become part of your game assets.
Step 1: Define NPC archetypes
Each archetype describes a population: a region, gender, typical height and weight distribution, and body build type. The API’s regional calibration profiles produce culturally specific body proportions — East Asian NPCs will differ from North American ones at the same height and weight, because the underlying population statistics differ.
// NPCArchetype.cs
using UnityEngine;
[System.Serializable]
public class NPCArchetype
{
public string name;
// API parameters
public string gender; // "male" or "female"
public string region; // GLOBAL | EUROPE | ASIA_PACIFIC | AFRICA |
// LATAM | INDIA | MIDDLE_EAST
public string buildType; // CIVILIAN | ATHLETIC | OVERWEIGHT
// Height distribution for this archetype (cm)
public float heightMeanCm;
public float heightStdDevCm;
// Weight distribution (kg)
public float weightMeanKg;
public float weightStdDevKg;
}
Example archetypes for a medieval fantasy city:
public static class CityArchetypes
{
public static readonly NPCArchetype CityGuard = new NPCArchetype {
name = "City Guard",
gender = "male",
region = "EUROPE",
buildType = "ATHLETIC",
heightMeanCm = 182f, heightStdDevCm = 4f,
weightMeanKg = 90f, weightStdDevKg = 8f,
};
public static readonly NPCArchetype MarketVendor = new NPCArchetype {
name = "Market Vendor",
gender = "female",
region = "EUROPE",
buildType = "CIVILIAN",
heightMeanCm = 163f, heightStdDevCm = 6f,
weightMeanKg = 66f, weightStdDevKg = 10f,
};
public static readonly NPCArchetype EasternMerchant = new NPCArchetype {
name = "Eastern Merchant",
gender = "male",
region = "ASIA_PACIFIC",
buildType = "CIVILIAN",
heightMeanCm = 172f, heightStdDevCm = 5f,
weightMeanKg = 70f, weightStdDevKg = 9f,
};
}
Step 2: Sample parameters with Gaussian distribution
Uniform random produces unrealistic uniformity. Real populations follow approximately normal distributions for height and weight. Box-Muller transform gives you Gaussian sampling without external libraries:
public static class MathUtils
{
private static System.Random rng = new System.Random();
public static float GaussianRandom(float mean, float stdDev)
{
// Box-Muller transform
float u1 = 1f - (float)rng.NextDouble();
float u2 = 1f - (float)rng.NextDouble();
float z = Mathf.Sqrt(-2f * Mathf.Log(u1)) * Mathf.Sin(2f * Mathf.PI * u2);
return mean + stdDev * z;
}
// Clamp to physiologically plausible range
public static float SampleHeight(NPCArchetype a) =>
Mathf.Clamp(GaussianRandom(a.heightMeanCm, a.heightStdDevCm), 145f, 215f);
public static float SampleWeight(NPCArchetype a) =>
Mathf.Clamp(GaussianRandom(a.weightMeanKg, a.weightStdDevKg), 40f, 160f);
}
Step 3: Batch API calls
DimensionsPot is stateless — every request is independent. This means you can run many calls in parallel, limited only by your rate tier. Batch them in groups of 10 with a brief delay between groups:
// NPCBodyGenerator.cs — attach to an Editor window or build pipeline tool
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Newtonsoft.Json;
public class NPCBodyGenerator
{
private static readonly HttpClient Http = new HttpClient();
private const string API_URL = "https://dimensionspot-bodysize-engine.p.rapidapi.com/v1/predict";
private const string RAPIDAPI_KEY = ""; // Load from EditorPrefs or env — never hardcode
[System.Serializable]
public class DimensionDetail
{
public float value;
public int confidence_score;
public string unit;
public string label;
}
[System.Serializable]
public class APIResponse
{
public Dictionary<string, DimensionDetail> body_dimensions;
}
[System.Serializable]
public class NPCBodyData
{
public string archetypeName;
public float heightMm;
public float weightKg;
public float shoulder_width_mm;
public float hip_circumference_mm;
public float chest_circumference_mm;
public float waist_circumference_mm;
public float inseam_length_mm;
public float body_height_mm;
}
public async Task<List<NPCBodyData>> GenerateBatch(NPCArchetype archetype, int count)
{
var results = new List<NPCBodyData>();
const int batchSize = 10;
for (int i = 0; i < count; i += batchSize)
{
int thisBatch = Math.Min(batchSize, count - i);
var tasks = new List<Task<NPCBodyData>>(thisBatch);
for (int j = 0; j < thisBatch; j++)
{
float heightCm = MathUtils.SampleHeight(archetype);
float weightKg = MathUtils.SampleWeight(archetype);
tasks.Add(FetchDimensions(archetype, heightCm, weightKg));
}
var batchResults = await Task.WhenAll(tasks);
results.AddRange(batchResults);
// Pause between batches — adjust based on your rate tier
if (i + batchSize < count)
await Task.Delay(150);
}
return results;
}
private async Task<NPCBodyData> FetchDimensions(NPCArchetype a, float heightCm, float weightKg)
{
var requestBody = new
{
input_data = new {
input_unit_system = "metric",
subject = new {
gender = a.gender,
input_origin_region = a.region,
},
anchors = new {
body_height = (int)(heightCm * 10), // cm → mm
body_mass = weightKg,
},
},
output_settings = new {
calculation = new {
calculation_model = "AUTO",
target_region = a.region,
body_build_type = a.buildType,
},
requested_dimensions = new { bundle = "FULL_BODY" },
output_format = new {
unit_system = "metric",
include_range_95 = false,
confidence_score_threshold = 0,
},
},
};
var json = JsonConvert.SerializeObject(requestBody);
var content = new StringContent(json, Encoding.UTF8, "application/json");
Http.DefaultRequestHeaders.Clear();
Http.DefaultRequestHeaders.Add("x-rapidapi-key", RAPIDAPI_KEY);
Http.DefaultRequestHeaders.Add("x-rapidapi-host", "dimensionspot-bodysize-engine.p.rapidapi.com");
var response = await Http.PostAsync(API_URL, content);
response.EnsureSuccessStatusCode();
var responseJson = await response.Content.ReadAsStringAsync();
var apiData = JsonConvert.DeserializeObject<APIResponse>(responseJson);
var dims = apiData.body_dimensions ?? new Dictionary<string, DimensionDetail>();
float GetValue(string key, float fallback) =>
dims.TryGetValue(key, out var d) ? d.value : fallback;
return new NPCBodyData {
archetypeName = a.name,
heightMm = heightCm * 10f,
weightKg = weightKg,
body_height_mm = heightCm * 10f,
shoulder_width_mm = GetValue("biacromial_breadth", 400f),
chest_circumference_mm = GetValue("chest_circumference", 920f),
waist_circumference_mm = GetValue("waist_circumference_natural", 780f),
hip_circumference_mm = GetValue("hip_circumference", 960f),
inseam_length_mm = GetValue("inseam_length", 790f),
};
}
}
Step 4: Apply dimensions to NPC bone rig
At runtime, assign the pre-generated body data to an NPC and scale its rig:
// NPCBodyScaler.cs — attach to each NPC prefab
using UnityEngine;
public class NPCBodyScaler : MonoBehaviour
{
// These values match your base mesh's dimensions exactly
[Header("Reference mesh dimensions (mm)")]
public float refHeight = 1750f;
public float refShoulderWidth = 400f;
public float refHipCirc = 940f;
public float refInseam = 790f;
private Animator _animator;
void Awake() => _animator = GetComponent<Animator>();
public void ApplyBodyData(NPCBodyGenerator.NPCBodyData d)
{
// 1. Overall height via root scale
float heightScale = d.body_height_mm / refHeight;
transform.localScale = new Vector3(1f, heightScale, 1f);
// 2. Shoulder width via spine bone
var spine = _animator.GetBoneTransform(HumanBodyBones.Spine);
if (spine != null)
{
float s = d.shoulder_width_mm / refShoulderWidth;
spine.localScale = new Vector3(s, 1f, s);
}
// 3. Hip width via hips bone
var hips = _animator.GetBoneTransform(HumanBodyBones.Hips);
if (hips != null)
{
float hipWidth = d.hip_circumference_mm / Mathf.PI;
float refHipWidth = refHipCirc / Mathf.PI;
float s = hipWidth / refHipWidth;
hips.localScale = new Vector3(s, 1f, s);
}
// 4. Leg proportions via upper leg bones
float legScale = d.inseam_length_mm / refInseam;
var leftLeg = _animator.GetBoneTransform(HumanBodyBones.LeftUpperLeg);
var rightLeg = _animator.GetBoneTransform(HumanBodyBones.RightUpperLeg);
if (leftLeg != null) leftLeg.localScale = new Vector3(1f, legScale, 1f);
if (rightLeg != null) rightLeg.localScale = new Vector3(1f, legScale, 1f);
}
}
Storing the results
Export the generated body data to JSON or ScriptableObjects during build time. Don’t regenerate at runtime unless you’re doing procedural world generation where NPCs are created on demand.
// Save generated batch to JSON file in project
using System.IO;
using UnityEditor;
void SaveBatch(List<NPCBodyGenerator.NPCBodyData> batch, string filename)
{
var json = JsonConvert.SerializeObject(batch, Formatting.Indented);
File.WriteAllText(Path.Combine(Application.dataPath, "GeneratedNPCs", filename), json);
AssetDatabase.Refresh();
}
At runtime, load from the JSON asset and assign to NPC instances as they spawn. This keeps generation off the critical path and makes your NPC population reproducible across builds.