Skip to content

Commit 73eb27a

Browse files
Merge pull request #1047 from CoplayDev/release/v9.6.6
chore: bump version to 9.6.6
2 parents 940831f + 4c05c4b commit 73eb27a

40 files changed

Lines changed: 614 additions & 162 deletions

.github/workflows/unity-tests.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ jobs:
7676
if: steps.detect.outputs.unity_ok == 'true'
7777
uses: game-ci/unity-test-runner@v4
7878
id: tests
79+
continue-on-error: true
7980
env:
8081
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
8182
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
@@ -86,6 +87,25 @@ jobs:
8687
unityVersion: ${{ matrix.unityVersion }}
8788
testMode: ${{ matrix.testMode }}
8889

90+
- name: Check test results
91+
if: steps.detect.outputs.unity_ok == 'true'
92+
run: |
93+
RESULTS_XML=$(find ${{ steps.tests.outputs.artifactsPath }} -name '*.xml' 2>/dev/null | head -1)
94+
if [ -z "$RESULTS_XML" ]; then
95+
echo "::error::No test results XML found — Unity may have crashed"
96+
exit 1
97+
fi
98+
FAILED=$(grep -oP 'failed="\K[0-9]+' "$RESULTS_XML" | head -1)
99+
PASSED=$(grep -oP 'passed="\K[0-9]+' "$RESULTS_XML" | head -1)
100+
TOTAL=$(grep -oP 'total="\K[0-9]+' "$RESULTS_XML" | head -1)
101+
INCONCLUSIVE=$(grep -oP 'inconclusive="\K[0-9]+' "$RESULTS_XML" | head -1)
102+
SKIPPED=$(grep -oP 'skipped="\K[0-9]+' "$RESULTS_XML" | head -1)
103+
echo "Results: $PASSED passed, $FAILED failed, $INCONCLUSIVE inconclusive, $SKIPPED skipped (total: $TOTAL)"
104+
if [ "$FAILED" != "0" ]; then
105+
echo "::error::$FAILED test(s) failed"
106+
exit 1
107+
fi
108+
89109
- uses: actions/upload-artifact@v4
90110
if: always() && steps.detect.outputs.unity_ok == 'true'
91111
with:

MCPForUnity/Editor/Helpers/ComponentOps.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,18 @@ internal static bool SetObjectReference(SerializedProperty prop, JToken value, o
748748
return AssignObjectReference(prop, resolved, null, out error);
749749
}
750750

751+
// Try as asset GUID (32-char hex string)
752+
if (strVal.Length == 32 && IsHexString(strVal))
753+
{
754+
string assetPath = AssetDatabase.GUIDToAssetPath(strVal);
755+
if (!string.IsNullOrEmpty(assetPath))
756+
{
757+
var resolved = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(assetPath);
758+
if (resolved != null)
759+
return AssignObjectReference(prop, resolved, null, out error);
760+
}
761+
}
762+
751763
// Fall back to scene hierarchy lookup by name.
752764
return ResolveSceneObjectByName(prop, strVal, null, out error);
753765
}
@@ -969,6 +981,16 @@ private static long GetSpriteFileId(Sprite sprite)
969981
return 0;
970982
}
971983
}
984+
985+
private static bool IsHexString(string str)
986+
{
987+
foreach (char c in str)
988+
{
989+
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')))
990+
return false;
991+
}
992+
return true;
993+
}
972994
}
973995
}
974996

MCPForUnity/Editor/Tools/ManageComponents.cs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,26 @@ private static object RemoveComponent(JObject @params, JToken targetToken, strin
146146
return new ErrorResponse($"Component type '{componentTypeName}' not found.");
147147
}
148148

149-
// Use ComponentOps for the actual operation
149+
int? componentIndex = ParamCoercion.CoerceIntNullable(@params["componentIndex"] ?? @params["component_index"]);
150+
if (componentIndex.HasValue)
151+
{
152+
var components = targetGo.GetComponents(type);
153+
if (componentIndex.Value < 0 || componentIndex.Value >= components.Length)
154+
return new ErrorResponse($"component_index {componentIndex.Value} out of range. Found {components.Length} '{componentTypeName}' component(s).");
155+
if (type == typeof(Transform) || type == typeof(RectTransform))
156+
return new ErrorResponse("Cannot remove Transform or RectTransform components.");
157+
Undo.DestroyObjectImmediate(components[componentIndex.Value]);
158+
EditorUtility.SetDirty(targetGo);
159+
MarkOwningSceneDirty(targetGo);
160+
return new
161+
{
162+
success = true,
163+
message = $"Component '{componentTypeName}' (index {componentIndex.Value}) removed from '{targetGo.name}'.",
164+
data = new { instanceID = targetGo.GetInstanceID(), componentIndex = componentIndex.Value }
165+
};
166+
}
167+
168+
// Use ComponentOps for the actual operation (removes first instance)
150169
bool removed = ComponentOps.RemoveComponent(targetGo, type, out string error);
151170
if (!removed)
152171
{
@@ -188,7 +207,19 @@ private static object SetProperty(JObject @params, JToken targetToken, string se
188207
return new ErrorResponse($"Component type '{componentType}' not found.");
189208
}
190209

191-
Component component = targetGo.GetComponent(type);
210+
int? componentIndex = ParamCoercion.CoerceIntNullable(@params["componentIndex"] ?? @params["component_index"]);
211+
Component component;
212+
if (componentIndex.HasValue)
213+
{
214+
var components = targetGo.GetComponents(type);
215+
if (componentIndex.Value < 0 || componentIndex.Value >= components.Length)
216+
return new ErrorResponse($"component_index {componentIndex.Value} out of range. Found {components.Length} '{componentType}' component(s).");
217+
component = components[componentIndex.Value];
218+
}
219+
else
220+
{
221+
component = targetGo.GetComponent(type);
222+
}
192223
if (component == null)
193224
{
194225
return new ErrorResponse($"Component '{componentType}' not found on '{targetGo.name}'.");

MCPForUnity/Editor/Tools/ManageScript.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1910,6 +1910,10 @@ private static bool TryComputeMethodSpan(
19101910
int probe = lineStart - 1;
19111911
while (probe > searchStart)
19121912
{
1913+
// Skip past line-ending chars so LastIndexOf finds the *previous* newline
1914+
while (probe > searchStart && (source[probe] == '\n' || source[probe] == '\r'))
1915+
probe--;
1916+
if (probe <= searchStart) break;
19131917
int prevNl = source.LastIndexOf('\n', probe);
19141918
if (prevNl < 0 || prevNl < searchStart) break;
19151919
string prev = source.Substring(prevNl + 1, attrStart - (prevNl + 1));
@@ -2740,16 +2744,18 @@ private static void CheckDuplicateMethodSignatures(string contents, System.Colle
27402744

27412745
// Step 3: Match method signatures on code-only text (includes => for expression-bodied)
27422746
var methodSigPattern = new Regex(
2743-
@"(?:(?:public|private|protected|internal)\s+)?(?:(?:static|virtual|override|abstract|sealed|async|new)\s+)*\S+\s+(\w+)\s*\(([^)]*)\)\s*(?:where\s+\S+\s*:\s*\S+\s*)?(?:[{;]|=>)",
2747+
@"(?:(?:public|private|protected|internal)\s+)?(?:(?:static|virtual|override|abstract|sealed|async|new)\s+)*(\S+)\s+(\w+)\s*\(([^)]*)\)\s*(?:where\s+\S+\s*:\s*\S+\s*)?(?:[{;]|=>)",
27442748
RegexOptions.Multiline | RegexOptions.CultureInvariant, TimeSpan.FromSeconds(2));
27452749
var sigMatches = methodSigPattern.Matches(codeOnly);
27462750
var seen = new System.Collections.Generic.Dictionary<string, int>(System.StringComparer.Ordinal);
27472751
foreach (Match sm in sigMatches)
27482752
{
2749-
string methodName = sm.Groups[1].Value;
2753+
string returnType = sm.Groups[1].Value;
2754+
string methodName = sm.Groups[2].Value;
2755+
if (string.Equals(returnType, "new", StringComparison.Ordinal)) continue; // constructor invocation, not a method declaration
27502756
if (IsCSharpKeyword(methodName)) continue;
2751-
int paramCount = CountTopLevelParams(sm.Groups[2].Value);
2752-
string paramTypes = ExtractParamTypes(sm.Groups[2].Value);
2757+
int paramCount = CountTopLevelParams(sm.Groups[3].Value);
2758+
string paramTypes = ExtractParamTypes(sm.Groups[3].Value);
27532759
string containingType = containingTypeArr[sm.Index];
27542760
string key = $"{containingType}/{methodName}/{paramCount}/{paramTypes}";
27552761
if (seen.TryGetValue(key, out _))

MCPForUnity/Editor/Tools/Physics/JointOps.cs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,17 @@ public static object ConfigureJoint(JObject @params)
161161
return new ErrorResponse($"Target GameObject '{targetStr}' not found.");
162162

163163
string jointTypeStr = p.Get("joint_type");
164-
Component joint = ResolveJoint(go, jointTypeStr);
164+
int? componentIndex = ParamCoercion.CoerceIntNullable(@params["componentIndex"] ?? @params["component_index"]);
165+
166+
if (componentIndex.HasValue && string.IsNullOrEmpty(jointTypeStr))
167+
return new ErrorResponse("component_index requires joint_type to be specified.");
168+
169+
Component joint = ResolveJoint(go, jointTypeStr, componentIndex, out int foundCount);
165170
if (joint == null)
166171
{
172+
if (componentIndex.HasValue && foundCount >= 0)
173+
return new ErrorResponse($"component_index {componentIndex.Value} out of range. Found {foundCount} joint(s) on '{go.name}'.");
174+
167175
if (!string.IsNullOrEmpty(jointTypeStr))
168176
return new ErrorResponse($"No joint of type '{jointTypeStr}' found on '{go.name}'.");
169177

@@ -324,6 +332,10 @@ public static object RemoveJoint(JObject @params)
324332
return new ErrorResponse($"Target GameObject '{targetStr}' not found.");
325333

326334
string jointTypeStr = p.Get("joint_type");
335+
int? componentIndex = ParamCoercion.CoerceIntNullable(@params["componentIndex"] ?? @params["component_index"]);
336+
337+
if (componentIndex.HasValue && string.IsNullOrEmpty(jointTypeStr))
338+
return new ErrorResponse("component_index requires joint_type to be specified.");
327339

328340
var jointsToRemove = new List<Component>();
329341

@@ -342,7 +354,17 @@ public static object RemoveJoint(JObject @params)
342354
}
343355

344356
var components = go.GetComponents(jointComponentType);
345-
jointsToRemove.AddRange(components);
357+
358+
if (componentIndex.HasValue)
359+
{
360+
if (componentIndex.Value < 0 || componentIndex.Value >= components.Length)
361+
return new ErrorResponse($"component_index {componentIndex.Value} out of range. Found {components.Length} '{jointComponentType.Name}' joint(s) on '{go.name}'.");
362+
jointsToRemove.Add(components[componentIndex.Value]);
363+
}
364+
else
365+
{
366+
jointsToRemove.AddRange(components);
367+
}
346368
}
347369
else
348370
{
@@ -403,16 +425,27 @@ private static GameObject FindTarget(JToken targetToken, string searchMethod)
403425
return GameObjectLookup.FindByTarget(targetToken, searchMethod ?? "by_name", true);
404426
}
405427

406-
private static Component ResolveJoint(GameObject go, string jointTypeStr)
428+
private static Component ResolveJoint(GameObject go, string jointTypeStr, int? index, out int foundCount)
407429
{
430+
foundCount = -1;
408431
if (!string.IsNullOrEmpty(jointTypeStr))
409432
{
410433
bool is2D = go.GetComponent<Rigidbody2D>() != null;
411434
var typeMap = is2D ? JointTypes2D : JointTypes3D;
412435
string key = jointTypeStr.ToLowerInvariant();
413436

414437
if (typeMap.TryGetValue(key, out Type jointType))
438+
{
439+
if (index.HasValue)
440+
{
441+
var components = go.GetComponents(jointType);
442+
foundCount = components.Length;
443+
if (index.Value < 0 || index.Value >= components.Length)
444+
return null;
445+
return components[index.Value];
446+
}
415447
return go.GetComponent(jointType);
448+
}
416449

417450
return null;
418451
}

MCPForUnity/Editor/Tools/Physics/PhysicsMaterialOps.cs

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ public static object Assign(JObject @params)
7979

8080
string searchMethod = p.Get("search_method") ?? "by_name";
8181
string colliderType = p.Get("collider_type");
82+
int? componentIndex = ParamCoercion.CoerceIntNullable(p.GetRaw("componentIndex") ?? p.GetRaw("component_index"));
8283

8384
var go = GameObjectLookup.FindByTarget(targetToken, searchMethod);
8485
if (go == null)
@@ -98,7 +99,7 @@ public static object Assign(JObject @params)
9899
// Try 3D colliders first
99100
if (mat3D != null)
100101
{
101-
var collider3D = FindCollider3D(go, colliderType);
102+
var collider3D = FindCollider3D(go, colliderType, componentIndex);
102103
if (collider3D != null)
103104
{
104105
Undo.RecordObject(collider3D, "Assign Physics Material");
@@ -116,12 +117,25 @@ public static object Assign(JObject @params)
116117
}
117118
};
118119
}
120+
if (componentIndex.HasValue)
121+
{
122+
var type3D = !string.IsNullOrEmpty(colliderType) ? UnityTypeResolver.ResolveComponent(colliderType) : typeof(Collider);
123+
if (type3D != null && typeof(Collider).IsAssignableFrom(type3D))
124+
{
125+
int count3D = go.GetComponents(type3D).Length;
126+
return new ErrorResponse($"component_index {componentIndex.Value} out of range. Found {count3D} '{type3D.Name}' collider(s) on '{go.name}'.");
127+
}
128+
else if (!string.IsNullOrEmpty(colliderType))
129+
{
130+
return new ErrorResponse($"Unknown or invalid 3D collider type: '{colliderType}'.");
131+
}
132+
}
119133
}
120134

121135
// Try 2D colliders
122136
if (mat2D != null)
123137
{
124-
var collider2D = FindCollider2D(go, colliderType);
138+
var collider2D = FindCollider2D(go, colliderType, componentIndex);
125139
if (collider2D != null)
126140
{
127141
Undo.RecordObject(collider2D, "Assign Physics Material 2D");
@@ -139,6 +153,19 @@ public static object Assign(JObject @params)
139153
}
140154
};
141155
}
156+
if (componentIndex.HasValue)
157+
{
158+
var type2D = !string.IsNullOrEmpty(colliderType) ? UnityTypeResolver.ResolveComponent(colliderType) : typeof(Collider2D);
159+
if (type2D != null && typeof(Collider2D).IsAssignableFrom(type2D))
160+
{
161+
int count2D = go.GetComponents(type2D).Length;
162+
return new ErrorResponse($"component_index {componentIndex.Value} out of range. Found {count2D} '{type2D.Name}' collider(s) on '{go.name}'.");
163+
}
164+
else if (!string.IsNullOrEmpty(colliderType))
165+
{
166+
return new ErrorResponse($"Unknown or invalid 2D collider type: '{colliderType}'.");
167+
}
168+
}
142169
}
143170

144171
return new ErrorResponse($"No suitable collider found on '{go.name}'.");
@@ -398,29 +425,63 @@ private static object Configure2D(string path, JObject properties)
398425
// Assign helpers
399426
// =====================================================================
400427

401-
private static Collider FindCollider3D(GameObject go, string colliderType)
428+
private static Collider FindCollider3D(GameObject go, string colliderType, int? index = null)
402429
{
403430
if (!string.IsNullOrEmpty(colliderType))
404431
{
405432
var type = UnityTypeResolver.ResolveComponent(colliderType);
406433
if (type != null && typeof(Collider).IsAssignableFrom(type))
434+
{
435+
if (index.HasValue)
436+
{
437+
var components = go.GetComponents(type);
438+
if (index.Value < 0 || index.Value >= components.Length)
439+
return null;
440+
return components[index.Value] as Collider;
441+
}
407442
return go.GetComponent(type) as Collider;
443+
}
408444
return null;
409445
}
410446

447+
if (index.HasValue)
448+
{
449+
var colliders = go.GetComponents<Collider>();
450+
if (index.Value < 0 || index.Value >= colliders.Length)
451+
return null;
452+
return colliders[index.Value];
453+
}
454+
411455
return go.GetComponent<Collider>();
412456
}
413457

414-
private static Collider2D FindCollider2D(GameObject go, string colliderType)
458+
private static Collider2D FindCollider2D(GameObject go, string colliderType, int? index = null)
415459
{
416460
if (!string.IsNullOrEmpty(colliderType))
417461
{
418462
var type = UnityTypeResolver.ResolveComponent(colliderType);
419463
if (type != null && typeof(Collider2D).IsAssignableFrom(type))
464+
{
465+
if (index.HasValue)
466+
{
467+
var components = go.GetComponents(type);
468+
if (index.Value < 0 || index.Value >= components.Length)
469+
return null;
470+
return components[index.Value] as Collider2D;
471+
}
420472
return go.GetComponent(type) as Collider2D;
473+
}
421474
return null;
422475
}
423476

477+
if (index.HasValue)
478+
{
479+
var colliders = go.GetComponents<Collider2D>();
480+
if (index.Value < 0 || index.Value >= colliders.Length)
481+
return null;
482+
return colliders[index.Value];
483+
}
484+
424485
return go.GetComponent<Collider2D>();
425486
}
426487

MCPForUnity/Editor/Tools/Profiler/Operations/CounterOps.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,11 @@ void Tick()
111111

112112
private static readonly string[] ValidCategories = new[]
113113
{
114-
"Render", "Scripts", "Memory", "Physics", "Physics2D", "Animation",
114+
"Render", "Scripts", "Memory", "Physics",
115+
#if UNITY_2022_2_OR_NEWER
116+
"Physics2D",
117+
#endif
118+
"Animation",
115119
"Audio", "Lighting", "Network", "Gui", "UI", "Ai", "Video",
116120
"Loading", "Input", "Vr", "Internal", "Particles", "FileIO", "VirtualTexturing"
117121
};
@@ -125,7 +129,9 @@ void Tick()
125129
case "scripts": return ProfilerCategory.Scripts;
126130
case "memory": return ProfilerCategory.Memory;
127131
case "physics": return ProfilerCategory.Physics;
132+
#if UNITY_2022_2_OR_NEWER
128133
case "physics2d": return ProfilerCategory.Physics2D;
134+
#endif
129135
case "animation": return ProfilerCategory.Animation;
130136
case "audio": return ProfilerCategory.Audio;
131137
case "lighting": return ProfilerCategory.Lighting;

0 commit comments

Comments
 (0)