Skip to content

Instantly share code, notes, and snippets.

@Myrkie
Last active June 1, 2025 23:51
Show Gist options
  • Save Myrkie/c99502d951650e9bccdd1080e670ed68 to your computer and use it in GitHub Desktop.
Save Myrkie/c99502d951650e9bccdd1080e670ed68 to your computer and use it in GitHub Desktop.
Automatically reorders all humanoid bones to be the first child to make VRChat's IK happy.
// originally by https://github.com/brandonvdongen/PFCTools
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
// ReSharper disable once CheckNamespace
[InitializeOnLoad]
public class HierarchyFix : AssetPostprocessor {
private static readonly HumanBodyBones[] Limbs = {
HumanBodyBones.LeftUpperArm,
HumanBodyBones.LeftLowerArm,
HumanBodyBones.RightUpperArm,
HumanBodyBones.RightLowerArm,
HumanBodyBones.LeftLowerLeg,
HumanBodyBones.RightLowerLeg
};
static HierarchyFix()
{
// force a reorder on scene start, since these chances don't stick with packed prefabs after restarts or scene changes
EditorApplication.delayCall += ReorderHierarchy;
EditorSceneManager.sceneOpened += (scene, mode) => {
EditorApplication.delayCall += ReorderHierarchy;
};
}
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) {
var hasModelFile = false;
foreach (var asset in importedAssets) {
if (asset.ToLower().EndsWith(".blend") || asset.ToLower().EndsWith(".fbx")) {
hasModelFile = true;
}
if (!hasModelFile) continue;
EditorApplication.delayCall -= ReorderHierarchy;
EditorApplication.delayCall += ReorderHierarchy;
break;
}
}
/// <summary>
/// Reorders all humanoid bones to be the first child.
/// </summary>
[MenuItem("Tools/Fix Hierarchy Order")]
private static void ReorderHierarchy() {
var descriptors = Object.FindObjectsOfType<VRCAvatarDescriptor>();
foreach (var descriptor in descriptors) {
var animator = descriptor.GetComponent<Animator>();
if (animator == null) return;
foreach (var bone in Limbs) {
var boneTransform = animator.GetBoneTransform(bone);
if (boneTransform == null) continue;
var ind = boneTransform.GetSiblingIndex();
if (ind == 0) continue;
Debug.Log($"Avatar: {descriptor.gameObject.name}: Humanoid Bone: {bone} is child {ind} instead of 0, fixing...", boneTransform);
boneTransform.SetAsFirstSibling();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment