- PlayerPoseController: add applyFootIK toggle (default off) and apply it to each pose playable so straight-knee idles render straight; Mecanim foot IK was bending the knees to plant the feet (162° instead of 180°). - Add Pose_Ball / Paintman-Ball animation and reorder the pose list. - Move StraightenKneesTool editor utility under Assets/Unused/Editor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
101 lines
4.1 KiB
C#
101 lines
4.1 KiB
C#
using System.Collections.Generic;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// Editor utility that pins the humanoid knee muscles to fully straight on selected animation clips.
|
|
///
|
|
/// On this project's auto-generated PaintmanAvatar, a fully straight leg corresponds to the
|
|
/// "Lower Leg Stretch" muscle sitting at its maximum (1.0); anything below reads as a visibly bent
|
|
/// knee (~8 deg per 0.1). Authoring tools (UMotion, Mixamo retargets) commonly land at ~0.93, which
|
|
/// shows as the "slightly bent knees on an idle that should be straight" symptom. This tool sets the
|
|
/// Left/Right Lower Leg Stretch curves to a constant 1.0 so the knees read as a true 180 deg.
|
|
///
|
|
/// Only native .anim humanoid clips can be edited — imported clips (FBX/Blend) are regenerated on
|
|
/// import, so their curves can't be changed here and are skipped with a warning.
|
|
/// </summary>
|
|
public static class StraightenKneesTool
|
|
{
|
|
private const string LeftStretch = "Left Lower Leg Stretch";
|
|
private const string RightStretch = "Right Lower Leg Stretch";
|
|
private const float StraightValue = 1f; // muscle value that maps to a fully straight knee on this avatar
|
|
|
|
private const string ToolsMenu = "Tools/Chameleon/Straighten Knees (Selected Clips)";
|
|
private const string AssetsMenu = "Assets/Chameleon/Straighten Knees";
|
|
|
|
[MenuItem(ToolsMenu, false, 100)]
|
|
[MenuItem(AssetsMenu, false, 100)]
|
|
private static void StraightenSelected()
|
|
{
|
|
List<AnimationClip> clips = GetSelectedClips();
|
|
if (clips.Count == 0)
|
|
{
|
|
EditorUtility.DisplayDialog("Straighten Knees",
|
|
"Select one or more native (.anim) humanoid AnimationClips in the Project window.", "OK");
|
|
return;
|
|
}
|
|
|
|
int changed = 0, skipped = 0;
|
|
foreach (AnimationClip clip in clips)
|
|
{
|
|
if (StraightenClip(clip)) changed++;
|
|
else skipped++;
|
|
}
|
|
|
|
if (changed > 0)
|
|
AssetDatabase.SaveAssets();
|
|
|
|
Debug.Log($"[Straighten Knees] Lower-leg stretch set to {StraightValue:0.##} on {changed} clip(s)" +
|
|
(skipped > 0 ? $", skipped {skipped} (non-.anim or non-humanoid)." : "."));
|
|
}
|
|
|
|
[MenuItem(ToolsMenu, true)]
|
|
[MenuItem(AssetsMenu, true)]
|
|
private static bool StraightenSelectedValidate() => GetSelectedClips().Count > 0;
|
|
|
|
private static bool StraightenClip(AnimationClip clip)
|
|
{
|
|
if (!clip)
|
|
return false;
|
|
|
|
string path = AssetDatabase.GetAssetPath(clip);
|
|
if (!path.EndsWith(".anim", System.StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
Debug.LogWarning($"[Straighten Knees] '{clip.name}' is an imported clip ({path}); its curves are " +
|
|
"regenerated on import and can't be edited. Make an editable .anim copy first.", clip);
|
|
return false;
|
|
}
|
|
if (!clip.humanMotion)
|
|
{
|
|
Debug.LogWarning($"[Straighten Knees] '{clip.name}' is not a humanoid clip; skipped.", clip);
|
|
return false;
|
|
}
|
|
|
|
Undo.RegisterCompleteObjectUndo(clip, "Straighten Knees");
|
|
SetMuscleConstant(clip, LeftStretch, StraightValue);
|
|
SetMuscleConstant(clip, RightStretch, StraightValue);
|
|
EditorUtility.SetDirty(clip);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>Replaces (or adds) the named muscle curve with a flat constant across the whole clip.</summary>
|
|
private static void SetMuscleConstant(AnimationClip clip, string muscle, float value)
|
|
{
|
|
var binding = EditorCurveBinding.FloatCurve(string.Empty, typeof(Animator), muscle);
|
|
float end = clip.length > 0f ? clip.length : 1f / Mathf.Max(1f, clip.frameRate);
|
|
var curve = new AnimationCurve(new Keyframe(0f, value), new Keyframe(end, value));
|
|
AnimationUtility.SetEditorCurve(clip, binding, curve);
|
|
}
|
|
|
|
private static List<AnimationClip> GetSelectedClips()
|
|
{
|
|
var result = new List<AnimationClip>();
|
|
foreach (Object o in Selection.objects)
|
|
{
|
|
if (o is AnimationClip clip && !result.Contains(clip))
|
|
result.Add(clip);
|
|
}
|
|
return result;
|
|
}
|
|
}
|