Skip to content

Commit fabd6aa

Browse files
committed
add more edit parameter
1 parent 6d51584 commit fabd6aa

2 files changed

Lines changed: 147 additions & 5 deletions

File tree

MCPForUnity/Editor/Tools/Animation/ControllerBlendTrees.cs

Lines changed: 115 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -352,12 +352,61 @@ public static object EditBlendTree(JObject @params)
352352
if (blendTree == null) return error;
353353

354354
var edits = @params["children"] as JArray;
355-
if (edits == null || edits.Count == 0)
356-
return new { success = false, message = "'children' array is required: [{index, position:[x,y], threshold?, timeScale?, cycleOffset?, mirror?}]" };
355+
356+
Undo.RecordObject(blendTree, "Edit Blend Tree");
357+
358+
// Tree-level properties (optional). Lets a blend tree be retyped/reparameterized
359+
// in place — needed to copy a source tree's type+params onto an existing state.
360+
int treeProps = 0;
361+
if (@params["blendType"] != null &&
362+
Enum.TryParse<BlendTreeType>(@params["blendType"].ToString(), true, out var bt))
363+
{ blendTree.blendType = bt; treeProps++; }
364+
if (@params["blendParameter"] != null)
365+
{ blendTree.blendParameter = @params["blendParameter"].ToString(); treeProps++; }
366+
if (@params["blendParameterY"] != null)
367+
{ blendTree.blendParameterY = @params["blendParameterY"].ToString(); treeProps++; }
368+
if (@params["useAutomaticThresholds"] != null)
369+
{ blendTree.useAutomaticThresholds = @params["useAutomaticThresholds"].ToObject<bool>(); treeProps++; }
370+
371+
// Optional: REPLACE the entire clip-child list (clears then re-adds). Lets a tree be
372+
// restructured in one call — needed when extracting children into a nested sub-tree,
373+
// since there is no per-child remove. Each item: {clipInstanceId|clipPath, position?:[x,y], threshold?}
374+
int replaced = -1;
375+
if (@params["setChildren"] is JArray setKids)
376+
{
377+
var fresh = new System.Collections.Generic.List<ChildMotion>();
378+
foreach (var kt in setKids)
379+
{
380+
if (!(kt is JObject kid)) continue;
381+
AnimationClip clip = null;
382+
string cInst = kid["clipInstanceId"]?.ToString();
383+
string cPath = kid["clipPath"]?.ToString();
384+
if (!string.IsNullOrEmpty(cInst))
385+
clip = UnityObjectIdCompat.InstanceIDFromString(cInst) as AnimationClip;
386+
else if (!string.IsNullOrEmpty(cPath))
387+
clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(AssetPathUtility.SanitizeAssetPath(cPath));
388+
if (clip == null)
389+
return new { success = false, message = $"setChildren clip not resolved (instanceId '{cInst}', path '{cPath}')" };
390+
391+
var cm = new ChildMotion { motion = clip, timeScale = 1f, directBlendParameter = blendTree.blendParameter };
392+
if (kid["position"] is JArray kpos && kpos.Count >= 2)
393+
cm.position = new Vector2(kpos[0].ToObject<float>(), kpos[1].ToObject<float>());
394+
if (kid["threshold"] != null) cm.threshold = kid["threshold"].ToObject<float>();
395+
if (kid["timeScale"] != null) cm.timeScale = kid["timeScale"].ToObject<float>();
396+
if (kid["cycleOffset"] != null) cm.cycleOffset = kid["cycleOffset"].ToObject<float>();
397+
if (kid["mirror"] != null) cm.mirror = kid["mirror"].ToObject<bool>();
398+
fresh.Add(cm);
399+
}
400+
blendTree.children = fresh.ToArray();
401+
replaced = fresh.Count;
402+
}
403+
404+
if ((edits == null || edits.Count == 0) && treeProps == 0 && replaced < 0 && @params["addChildTree"] == null)
405+
return new { success = false, message = "Provide tree-level props, 'children' (index edits), 'setChildren' (replace all), and/or 'addChildTree' (nested)." };
357406

358407
// ChildMotion is a struct; must reassign the whole array.
359408
var children = blendTree.children;
360-
Undo.RecordObject(blendTree, "Edit Blend Tree");
409+
if (edits == null) edits = new JArray();
361410

362411
int applied = 0;
363412
foreach (var token in edits)
@@ -385,14 +434,75 @@ public static object EditBlendTree(JObject @params)
385434
}
386435

387436
blendTree.children = children;
437+
438+
// Optional: create a NESTED child blend tree inside this tree and populate it.
439+
// This is the only way to reach a true 3rd blend dimension (Unity trees are max 2D);
440+
// the nested tree blends on its own parameter, independent of the parent's axes.
441+
// Shape: addChildTree: { name?, position:[x,y], threshold?, blendType, blendParameter,
442+
// blendParameterY?, useAutomaticThresholds?, children:[{clipInstanceId|clipPath, threshold?, position?:[x,y]}] }
443+
object nestedInfo = null;
444+
if (@params["addChildTree"] is JObject ct)
445+
{
446+
BlendTree child;
447+
if (blendTree.blendType == BlendTreeType.Simple1D)
448+
{
449+
float thr = ct["threshold"]?.ToObject<float>() ?? 0f;
450+
child = blendTree.CreateBlendTreeChild(thr);
451+
}
452+
else
453+
{
454+
if (!(ct["position"] is JArray cpos) || cpos.Count < 2)
455+
return new { success = false, message = "addChildTree.position [x,y] is required when parent is a 2D tree" };
456+
child = blendTree.CreateBlendTreeChild(new Vector2(cpos[0].ToObject<float>(), cpos[1].ToObject<float>()));
457+
}
458+
459+
child.name = ct["name"]?.ToString() ?? "Look Blend Tree";
460+
child.hideFlags = HideFlags.HideInHierarchy;
461+
if (ct["blendType"] != null && Enum.TryParse<BlendTreeType>(ct["blendType"].ToString(), true, out var cbt))
462+
child.blendType = cbt;
463+
if (ct["blendParameter"] != null) child.blendParameter = ct["blendParameter"].ToString();
464+
if (ct["blendParameterY"] != null) child.blendParameterY = ct["blendParameterY"].ToString();
465+
if (ct["useAutomaticThresholds"] != null) child.useAutomaticThresholds = ct["useAutomaticThresholds"].ToObject<bool>();
466+
467+
int nestedAdded = 0;
468+
if (ct["children"] is JArray nestedKids)
469+
{
470+
foreach (var kt in nestedKids)
471+
{
472+
if (!(kt is JObject kid)) continue;
473+
AnimationClip clip = null;
474+
string cInst = kid["clipInstanceId"]?.ToString();
475+
string cPath = kid["clipPath"]?.ToString();
476+
if (!string.IsNullOrEmpty(cInst))
477+
clip = UnityObjectIdCompat.InstanceIDFromString(cInst) as AnimationClip;
478+
else if (!string.IsNullOrEmpty(cPath))
479+
clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(AssetPathUtility.SanitizeAssetPath(cPath));
480+
if (clip == null)
481+
return new { success = false, message = $"addChildTree child clip not resolved (instanceId '{cInst}', path '{cPath}')" };
482+
483+
if (child.blendType == BlendTreeType.Simple1D)
484+
child.AddChild(clip, kid["threshold"]?.ToObject<float>() ?? 0f);
485+
else if (kid["position"] is JArray kpos && kpos.Count >= 2)
486+
child.AddChild(clip, new Vector2(kpos[0].ToObject<float>(), kpos[1].ToObject<float>()));
487+
else
488+
child.AddChild(clip);
489+
nestedAdded++;
490+
}
491+
}
492+
493+
EditorUtility.SetDirty(child);
494+
nestedInfo = new { name = child.name, blendType = child.blendType.ToString(), blendParameter = child.blendParameter, childCount = child.children.Length, added = nestedAdded };
495+
}
496+
388497
EditorUtility.SetDirty(blendTree);
389498
AssetDatabase.SaveAssets();
390499

391500
return new
392501
{
393502
success = true,
394-
message = $"Applied {applied} edit(s) to blend tree '{blendTree.name}'",
395-
data = new { name = blendTree.name, edited = applied, childCount = children.Length }
503+
nestedChildTree = nestedInfo,
504+
message = $"Applied {treeProps} tree prop(s), {applied} child edit(s), replaced={replaced} on blend tree '{blendTree.name}'",
505+
data = new { name = blendTree.name, treeProps, edited = applied, replaced, childCount = blendTree.children.Length, blendType = blendTree.blendType.ToString() }
396506
};
397507
}
398508
}

MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,30 @@ public static object AddTransition(JObject @params)
174174
float exitTime = @params["exitTime"]?.ToObject<float>() ?? 0.75f;
175175
transition.exitTime = exitTime;
176176

177+
// Optional fields: only override Unity's defaults when supplied, so a faithful
178+
// remove+re-add round-trip (paired with get_info) loses nothing. Names match the
179+
// keys emitted by GetInfo.
180+
if (@params["offset"] != null)
181+
transition.offset = @params["offset"].ToObject<float>();
182+
if (@params["hasFixedDuration"] != null)
183+
transition.hasFixedDuration = @params["hasFixedDuration"].ToObject<bool>();
184+
if (@params["canTransitionToSelf"] != null)
185+
transition.canTransitionToSelf = @params["canTransitionToSelf"].ToObject<bool>();
186+
if (@params["orderedInterruption"] != null)
187+
transition.orderedInterruption = @params["orderedInterruption"].ToObject<bool>();
188+
if (@params["interruptionSource"] != null)
189+
{
190+
if (Enum.TryParse<TransitionInterruptionSource>(@params["interruptionSource"].ToString(), true, out var src))
191+
transition.interruptionSource = src;
192+
}
193+
if (@params["mute"] != null)
194+
transition.mute = @params["mute"].ToObject<bool>();
195+
if (@params["solo"] != null)
196+
transition.solo = @params["solo"].ToObject<bool>();
197+
string transitionName = @params["name"]?.ToString();
198+
if (!string.IsNullOrEmpty(transitionName))
199+
transition.name = transitionName;
200+
177201
// Add conditions
178202
JToken conditionsToken = @params["conditions"];
179203
int conditionCount = 0;
@@ -397,10 +421,18 @@ public static object GetInfo(JObject @params)
397421

398422
transitions.Add(new
399423
{
424+
name = t.name,
400425
destinationState = t.destinationState?.name,
401426
hasExitTime = t.hasExitTime,
402427
exitTime = t.exitTime,
403428
duration = t.duration,
429+
offset = t.offset,
430+
hasFixedDuration = t.hasFixedDuration,
431+
canTransitionToSelf = t.canTransitionToSelf,
432+
orderedInterruption = t.orderedInterruption,
433+
interruptionSource = t.interruptionSource.ToString(),
434+
mute = t.mute,
435+
solo = t.solo,
404436
conditionCount = t.conditions.Length,
405437
conditions
406438
});

0 commit comments

Comments
 (0)