From 002dcab044db684b38a04e45685882300f27a27c Mon Sep 17 00:00:00 2001 From: Ark Tarusov Date: Tue, 16 Jun 2026 21:00:17 +0200 Subject: [PATCH 1/4] guard physics and screen capture usage behind module version defines --- MCPForUnity/Editor/Helpers/ComponentOps.cs | 4 + .../Editor/Helpers/GameObjectSerializer.cs | 2 + MCPForUnity/Editor/MCPForUnity.Editor.asmdef | 15 ++++ .../GameObjects/GameObjectComponentHelpers.cs | 4 + MCPForUnity/Editor/Tools/ManageAsset.cs | 10 +++ MCPForUnity/Editor/Tools/ManageScene.cs | 6 +- .../Tools/Physics/CollisionMatrixOps.cs | 41 +++++++++- MCPForUnity/Editor/Tools/Physics/JointOps.cs | 74 ++++++++++++++++++- .../Editor/Tools/Physics/ManagePhysics.cs | 7 ++ .../Editor/Tools/Physics/PhysicsForceOps.cs | 25 ++++++- .../Tools/Physics/PhysicsMaterialOps.cs | 40 ++++++++++ .../Editor/Tools/Physics/PhysicsQueryOps.cs | 74 +++++++++++++++++++ .../Tools/Physics/PhysicsRigidbodyOps.cs | 44 +++++++++++ .../Tools/Physics/PhysicsSettingsOps.cs | 50 +++++++++---- .../Tools/Physics/PhysicsSimulationOps.cs | 16 ++++ .../Tools/Physics/PhysicsValidationOps.cs | 26 +++++++ .../Runtime/Helpers/ScreenshotUtility.cs | 19 ++++- .../Runtime/Helpers/UnityPhysicsCompat.cs | 19 +++-- .../Runtime/MCPForUnity.Runtime.asmdef | 18 ++++- 19 files changed, 466 insertions(+), 28 deletions(-) diff --git a/MCPForUnity/Editor/Helpers/ComponentOps.cs b/MCPForUnity/Editor/Helpers/ComponentOps.cs index 60069079a..ae4d0d40e 100644 --- a/MCPForUnity/Editor/Helpers/ComponentOps.cs +++ b/MCPForUnity/Editor/Helpers/ComponentOps.cs @@ -376,6 +376,9 @@ internal static FieldInfo FindSerializedFieldInHierarchy(Type type, string field private static string CheckPhysicsConflict(GameObject target, Type componentType) { + // A 2D-vs-3D conflict is only possible when both physics modules are installed; + // if either is absent, its component types can't exist, so there's nothing to check. +#if MCP_HAS_PHYSICS && MCP_HAS_PHYSICS_2D bool isAdding2DPhysics = typeof(Rigidbody2D).IsAssignableFrom(componentType) || typeof(Collider2D).IsAssignableFrom(componentType); @@ -398,6 +401,7 @@ private static string CheckPhysicsConflict(GameObject target, Type componentType return $"Cannot add 3D physics component '{componentType.Name}' because the GameObject '{target.name}' already has a 2D Rigidbody or Collider."; } } +#endif return null; } diff --git a/MCPForUnity/Editor/Helpers/GameObjectSerializer.cs b/MCPForUnity/Editor/Helpers/GameObjectSerializer.cs index f97113618..4237f1cb0 100644 --- a/MCPForUnity/Editor/Helpers/GameObjectSerializer.cs +++ b/MCPForUnity/Editor/Helpers/GameObjectSerializer.cs @@ -528,6 +528,7 @@ public static object GetComponentData(Component c, bool includeNonPublicSerializ } // --- End Skip Transform Properties --- +#if MCP_HAS_PHYSICS // --- Skip Collider properties that cause native crashes via PhysX --- if (typeof(Collider).IsAssignableFrom(componentType) && propName == "GeometryHolder") @@ -535,6 +536,7 @@ public static object GetComponentData(Component c, bool includeNonPublicSerializ skipProperty = true; } // --- End Skip Collider Properties --- +#endif // Skip if flagged if (skipProperty) diff --git a/MCPForUnity/Editor/MCPForUnity.Editor.asmdef b/MCPForUnity/Editor/MCPForUnity.Editor.asmdef index e8f0ecd6d..e0e6bdb1c 100644 --- a/MCPForUnity/Editor/MCPForUnity.Editor.asmdef +++ b/MCPForUnity/Editor/MCPForUnity.Editor.asmdef @@ -19,6 +19,21 @@ "name": "com.unity.visualeffectgraph", "expression": "0.0.0", "define": "UNITY_VFX_GRAPH" + }, + { + "name": "com.unity.modules.physics", + "expression": "1.0.0", + "define": "MCP_HAS_PHYSICS" + }, + { + "name": "com.unity.modules.physics2d", + "expression": "1.0.0", + "define": "MCP_HAS_PHYSICS_2D" + }, + { + "name": "com.unity.modules.screencapture", + "expression": "1.0.0", + "define": "MCP_HAS_SCREEN_CAPTURE" } ], "noEngineReferences": false diff --git a/MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs b/MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs index 1e7063ae6..aa95c69c2 100644 --- a/MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs +++ b/MCPForUnity/Editor/Tools/GameObjects/GameObjectComponentHelpers.cs @@ -32,6 +32,9 @@ internal static object AddComponentInternal(GameObject targetGo, string typeName return new ErrorResponse("Cannot add another Transform component."); } + // A 2D-vs-3D conflict is only possible when both physics modules are installed; + // if either is absent, its component types can't exist, so there's nothing to check. +#if MCP_HAS_PHYSICS && MCP_HAS_PHYSICS_2D bool isAdding2DPhysics = typeof(Rigidbody2D).IsAssignableFrom(componentType) || typeof(Collider2D).IsAssignableFrom(componentType); bool isAdding3DPhysics = typeof(Rigidbody).IsAssignableFrom(componentType) || typeof(Collider).IsAssignableFrom(componentType); @@ -49,6 +52,7 @@ internal static object AddComponentInternal(GameObject targetGo, string typeName return new ErrorResponse($"Cannot add 3D physics component '{typeName}' because the GameObject '{targetGo.name}' already has a 2D Rigidbody or Collider."); } } +#endif Component existingComponent = targetGo.GetComponent(componentType); if (existingComponent != null && !AllowsMultiple(componentType)) diff --git a/MCPForUnity/Editor/Tools/ManageAsset.cs b/MCPForUnity/Editor/Tools/ManageAsset.cs index 69aaf53cc..ea1d6e3a0 100644 --- a/MCPForUnity/Editor/Tools/ManageAsset.cs +++ b/MCPForUnity/Editor/Tools/ManageAsset.cs @@ -10,6 +10,7 @@ using MCPForUnity.Editor.Tools; using MCPForUnity.Runtime.Helpers; +#if MCP_HAS_PHYSICS #if UNITY_6000_0_OR_NEWER using PhysicsMaterialType = UnityEngine.PhysicsMaterial; using PhysicsMaterialCombine = UnityEngine.PhysicsMaterialCombine; @@ -17,6 +18,7 @@ using PhysicsMaterialType = UnityEngine.PhysicMaterial; using PhysicsMaterialCombine = UnityEngine.PhysicMaterialCombine; #endif +#endif namespace MCPForUnity.Editor.Tools { @@ -224,11 +226,17 @@ private static object CreateAsset(JObject @params) } else if (lowerAssetType == "physicsmaterial") { +#if MCP_HAS_PHYSICS PhysicsMaterialType pmat = new PhysicsMaterialType(); if (properties != null) ApplyPhysicsMaterialProperties(pmat, properties); AssetDatabase.CreateAsset(pmat, fullPath); newAsset = pmat; +#else + return new ErrorResponse( + "Cannot create a PhysicsMaterial: the Physics module (com.unity.modules.physics) is not installed. " + + "Enable it via Window > Package Manager > Built-in > Physics."); +#endif } else if (lowerAssetType == "prefab") { @@ -876,6 +884,7 @@ private static void EnsureDirectoryExists(string directoryPath) +#if MCP_HAS_PHYSICS /// /// Applies properties from JObject to a PhysicsMaterial. /// @@ -946,6 +955,7 @@ private static bool ApplyPhysicsMaterialProperties(PhysicsMaterialType pmat, JOb return modified; } +#endif /// /// Generic helper to set properties on any UnityEngine.Object using reflection. diff --git a/MCPForUnity/Editor/Tools/ManageScene.cs b/MCPForUnity/Editor/Tools/ManageScene.cs index c3c66b3fa..f202440be 100644 --- a/MCPForUnity/Editor/Tools/ManageScene.cs +++ b/MCPForUnity/Editor/Tools/ManageScene.cs @@ -1460,8 +1460,9 @@ private static bool TryGetRendererBounds(GameObject target, out Bounds bounds) private static bool TryGetColliderBounds(GameObject target, out Bounds bounds) { bounds = default(Bounds); - var colliders = target.GetComponentsInChildren(true); bool hasBounds = false; +#if MCP_HAS_PHYSICS + var colliders = target.GetComponentsInChildren(true); foreach (var collider in colliders) { if (collider == null || !collider.gameObject.activeInHierarchy) @@ -1477,7 +1478,9 @@ private static bool TryGetColliderBounds(GameObject target, out Bounds bounds) bounds.Encapsulate(collider.bounds); } } +#endif +#if MCP_HAS_PHYSICS_2D var colliders2D = target.GetComponentsInChildren(true); foreach (var collider in colliders2D) { @@ -1494,6 +1497,7 @@ private static bool TryGetColliderBounds(GameObject target, out Bounds bounds) bounds.Encapsulate(collider.bounds); } } +#endif return hasBounds; } diff --git a/MCPForUnity/Editor/Tools/Physics/CollisionMatrixOps.cs b/MCPForUnity/Editor/Tools/Physics/CollisionMatrixOps.cs index 3482d47aa..00bcd8234 100644 --- a/MCPForUnity/Editor/Tools/Physics/CollisionMatrixOps.cs +++ b/MCPForUnity/Editor/Tools/Physics/CollisionMatrixOps.cs @@ -16,6 +16,19 @@ public static object GetCollisionMatrix(JObject @params) if (dimension != "3d" && dimension != "2d") return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'."); + if (dimension == "2d") + { +#if !MCP_HAS_PHYSICS_2D + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif + } + else + { +#if !MCP_HAS_PHYSICS + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif + } + var layers = new List(); var populatedIndices = new List(); @@ -38,9 +51,23 @@ public static object GetCollisionMatrix(JObject @params) { if (j > i) continue; string nameB = LayerMask.LayerToName(j); - bool collides = dimension == "2d" - ? !Physics2D.GetIgnoreLayerCollision(i, j) - : !UnityEngine.Physics.GetIgnoreLayerCollision(i, j); + bool collides; + if (dimension == "2d") + { +#if MCP_HAS_PHYSICS_2D + collides = !Physics2D.GetIgnoreLayerCollision(i, j); +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif + } + else + { +#if MCP_HAS_PHYSICS + collides = !UnityEngine.Physics.GetIgnoreLayerCollision(i, j); +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif + } row[nameB] = collides; } @@ -83,13 +110,21 @@ public static object SetCollisionMatrix(JObject @params) if (dimension == "2d") { +#if MCP_HAS_PHYSICS_2D Physics2D.IgnoreLayerCollision(layerA, layerB, !collide); MarkSettingsDirty("ProjectSettings/Physics2DSettings.asset"); +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif } else { +#if MCP_HAS_PHYSICS UnityEngine.Physics.IgnoreLayerCollision(layerA, layerB, !collide); MarkSettingsDirty("ProjectSettings/DynamicsManager.asset"); +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } string nameA = LayerMask.LayerToName(layerA); diff --git a/MCPForUnity/Editor/Tools/Physics/JointOps.cs b/MCPForUnity/Editor/Tools/Physics/JointOps.cs index e367d2dec..9aaf957d5 100644 --- a/MCPForUnity/Editor/Tools/Physics/JointOps.cs +++ b/MCPForUnity/Editor/Tools/Physics/JointOps.cs @@ -13,15 +13,18 @@ internal static class JointOps { private static readonly Dictionary JointTypes3D = new Dictionary { +#if MCP_HAS_PHYSICS { "fixed", typeof(FixedJoint) }, { "hinge", typeof(HingeJoint) }, { "spring", typeof(SpringJoint) }, { "character", typeof(CharacterJoint) }, { "configurable", typeof(ConfigurableJoint) } +#endif }; private static readonly Dictionary JointTypes2D = new Dictionary { +#if MCP_HAS_PHYSICS_2D { "distance", typeof(DistanceJoint2D) }, { "fixed", typeof(FixedJoint2D) }, { "friction", typeof(FrictionJoint2D) }, @@ -31,6 +34,7 @@ internal static class JointOps { "spring", typeof(SpringJoint2D) }, { "target", typeof(TargetJoint2D) }, { "wheel", typeof(WheelJoint2D) } +#endif }; public static object AddJoint(JObject @params) @@ -52,8 +56,16 @@ public static object AddJoint(JObject @params) return new ErrorResponse($"Target GameObject '{targetStr}' not found."); string dimensionParam = p.Get("dimension")?.ToLowerInvariant(); +#if MCP_HAS_PHYSICS bool is3D = go.GetComponent() != null; +#else + bool is3D = false; +#endif +#if MCP_HAS_PHYSICS_2D bool has2DRb = go.GetComponent() != null; +#else + bool has2DRb = false; +#endif bool is2D; if (dimensionParam == "2d") @@ -102,13 +114,21 @@ public static object AddJoint(JObject @params) if (is2D) { +#if MCP_HAS_PHYSICS_2D if (connectedGo.GetComponent() == null) return new ErrorResponse($"Connected body '{connectedGo.name}' has no Rigidbody2D."); +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif } else { +#if MCP_HAS_PHYSICS if (connectedGo.GetComponent() == null) return new ErrorResponse($"Connected body '{connectedGo.name}' has no Rigidbody."); +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } } @@ -120,9 +140,17 @@ public static object AddJoint(JObject @params) if (connectedGo != null) { if (is2D) + { +#if MCP_HAS_PHYSICS_2D ((Joint2D)joint).connectedBody = connectedGo.GetComponent(); +#endif + } else + { +#if MCP_HAS_PHYSICS ((Joint)joint).connectedBody = connectedGo.GetComponent(); +#endif + } } // Set properties via reflection if provided @@ -177,9 +205,15 @@ public static object ConfigureJoint(JObject @params) return new ErrorResponse($"No joint of type '{jointTypeStr}' found on '{go.name}'."); // Check if multiple joints exist to give a better error + int total = 0; +#if MCP_HAS_PHYSICS var joints3D = go.GetComponents(); + total += joints3D.Length; +#endif +#if MCP_HAS_PHYSICS_2D var joints2D = go.GetComponents(); - int total = joints3D.Length + joints2D.Length; + total += joints2D.Length; +#endif if (total > 1) return new ErrorResponse($"Multiple joints found on '{go.name}' ({total} total). Specify 'joint_type' to target a specific joint."); @@ -194,6 +228,7 @@ public static object ConfigureJoint(JObject @params) var motorToken = p.GetRaw("motor") as JObject; if (motorToken != null) { +#if MCP_HAS_PHYSICS if (joint is HingeJoint hingeForMotor) { var motor = hingeForMotor.motor; @@ -211,12 +246,16 @@ public static object ConfigureJoint(JObject @params) { return new ErrorResponse($"Motor configuration is only supported on HingeJoint, not {joint.GetType().Name}."); } +#else + return new ErrorResponse($"Motor configuration is only supported on HingeJoint, not {joint.GetType().Name}."); +#endif } // Limits configuration (HingeJoint) var limitsToken = p.GetRaw("limits") as JObject; if (limitsToken != null) { +#if MCP_HAS_PHYSICS if (joint is HingeJoint hingeForLimits) { var limits = hingeForLimits.limits; @@ -234,12 +273,16 @@ public static object ConfigureJoint(JObject @params) { return new ErrorResponse($"Limits configuration is only supported on HingeJoint, not {joint.GetType().Name}."); } +#else + return new ErrorResponse($"Limits configuration is only supported on HingeJoint, not {joint.GetType().Name}."); +#endif } // Spring configuration (HingeJoint / SpringJoint) var springToken = p.GetRaw("spring") as JObject; if (springToken != null) { +#if MCP_HAS_PHYSICS if (joint is HingeJoint hingeForSpring) { var spring = hingeForSpring.spring; @@ -267,12 +310,16 @@ public static object ConfigureJoint(JObject @params) { return new ErrorResponse($"Spring configuration is only supported on HingeJoint/SpringJoint, not {joint.GetType().Name}."); } +#else + return new ErrorResponse($"Spring configuration is only supported on HingeJoint/SpringJoint, not {joint.GetType().Name}."); +#endif } // Drive configuration (ConfigurableJoint) var driveToken = p.GetRaw("drive") as JObject; if (driveToken != null) { +#if MCP_HAS_PHYSICS if (joint is ConfigurableJoint configJoint) { var xDriveToken = driveToken["xDrive"] as JObject; @@ -293,6 +340,9 @@ public static object ConfigureJoint(JObject @params) { return new ErrorResponse($"Drive configuration is only supported on ConfigurableJoint, not {joint.GetType().Name}."); } +#else + return new ErrorResponse($"Drive configuration is only supported on ConfigurableJoint, not {joint.GetType().Name}."); +#endif } // Direct property setting @@ -343,7 +393,11 @@ public static object RemoveJoint(JObject @params) if (!string.IsNullOrEmpty(jointTypeStr)) { // Remove specific joint type +#if MCP_HAS_PHYSICS_2D bool is2D = go.GetComponent() != null; +#else + bool is2D = false; +#endif var typeMap = is2D ? JointTypes2D : JointTypes3D; string key = jointTypeStr.ToLowerInvariant(); @@ -370,10 +424,14 @@ public static object RemoveJoint(JObject @params) else { // Remove ALL joints (both 3D and 2D) +#if MCP_HAS_PHYSICS var joints3D = go.GetComponents(); - var joints2D = go.GetComponents(); jointsToRemove.AddRange(joints3D); +#endif +#if MCP_HAS_PHYSICS_2D + var joints2D = go.GetComponents(); jointsToRemove.AddRange(joints2D); +#endif } if (jointsToRemove.Count == 0) @@ -431,7 +489,11 @@ private static Component ResolveJoint(GameObject go, string jointTypeStr, int? i foundCount = -1; if (!string.IsNullOrEmpty(jointTypeStr)) { +#if MCP_HAS_PHYSICS_2D bool is2D = go.GetComponent() != null; +#else + bool is2D = false; +#endif var typeMap = is2D ? JointTypes2D : JointTypes3D; string key = jointTypeStr.ToLowerInvariant(); @@ -452,8 +514,16 @@ private static Component ResolveJoint(GameObject go, string jointTypeStr, int? i } // Auto-detect: find the single joint on the GO +#if MCP_HAS_PHYSICS var joints3D = go.GetComponents(); +#else + var joints3D = Array.Empty(); +#endif +#if MCP_HAS_PHYSICS_2D var joints2D = go.GetComponents(); +#else + var joints2D = Array.Empty(); +#endif int totalCount = joints3D.Length + joints2D.Length; if (totalCount == 1) diff --git a/MCPForUnity/Editor/Tools/Physics/ManagePhysics.cs b/MCPForUnity/Editor/Tools/Physics/ManagePhysics.cs index 71b63ccb9..b1638498c 100644 --- a/MCPForUnity/Editor/Tools/Physics/ManagePhysics.cs +++ b/MCPForUnity/Editor/Tools/Physics/ManagePhysics.cs @@ -18,6 +18,12 @@ public static object HandleCommand(JObject @params) if (string.IsNullOrEmpty(action)) return new ErrorResponse("'action' parameter is required."); +#if !(MCP_HAS_PHYSICS || MCP_HAS_PHYSICS_2D) + return new ErrorResponse( + "Physics tools are unavailable: neither the Physics (com.unity.modules.physics) nor " + + "Physics 2D (com.unity.modules.physics2d) built-in module is installed. " + + "Enable one via Window > Package Manager > Built-in."); +#else try { switch (action) @@ -103,6 +109,7 @@ public static object HandleCommand(JObject @params) new { stackTrace = ex.StackTrace } ); } +#endif } } } diff --git a/MCPForUnity/Editor/Tools/Physics/PhysicsForceOps.cs b/MCPForUnity/Editor/Tools/Physics/PhysicsForceOps.cs index 80806c51e..e158fb7cf 100644 --- a/MCPForUnity/Editor/Tools/Physics/PhysicsForceOps.cs +++ b/MCPForUnity/Editor/Tools/Physics/PhysicsForceOps.cs @@ -24,8 +24,16 @@ public static object ApplyForce(JObject @params) // Detect dimension string dimensionParam = p.Get("dimension")?.ToLowerInvariant(); +#if MCP_HAS_PHYSICS bool has3DRb = go.GetComponent() != null; +#else + bool has3DRb = false; +#endif +#if MCP_HAS_PHYSICS_2D bool has2DRb = go.GetComponent() != null; +#else + bool has2DRb = false; +#endif bool is2D; if (dimensionParam == "2d") @@ -44,15 +52,19 @@ public static object ApplyForce(JObject @params) // Validate not kinematic if (is2D) { +#if MCP_HAS_PHYSICS_2D var rb2d = go.GetComponent(); if (rb2d.bodyType == RigidbodyType2D.Kinematic) return new ErrorResponse($"Cannot apply force to kinematic Rigidbody on '{go.name}'."); +#endif } else { +#if MCP_HAS_PHYSICS var rb = go.GetComponent(); if (rb.isKinematic) return new ErrorResponse($"Cannot apply force to kinematic Rigidbody on '{go.name}'."); +#endif } string forceType = (p.Get("force_type") ?? "normal").ToLowerInvariant(); @@ -87,6 +99,7 @@ private static object ApplyNormalForce(ToolParams p, GameObject go, bool is2D) if (is2D) { +#if MCP_HAS_PHYSICS_2D ForceMode2D mode2d = ForceMode2D.Force; if (!string.IsNullOrEmpty(modeStr)) { @@ -131,9 +144,13 @@ private static object ApplyNormalForce(ToolParams p, GameObject go, bool is2D) responseData["torque"] = torqueFloat; applied.Add("torque"); } +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif } else { +#if MCP_HAS_PHYSICS ForceMode mode = ForceMode.Force; if (!string.IsNullOrEmpty(modeStr)) { @@ -188,6 +205,9 @@ private static object ApplyNormalForce(ToolParams p, GameObject go, bool is2D) responseData["torque"] = new[] { torqueVec.x, torqueVec.y, torqueVec.z }; applied.Add("torque"); } +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } string appliedStr = string.Join(" and ", applied); @@ -203,7 +223,7 @@ private static object ApplyExplosionForce(ToolParams p, GameObject go, bool is2D { if (is2D) return new ErrorResponse("Explosion force is only available for 3D physics."); - +#if MCP_HAS_PHYSICS float? explosionForce = p.GetFloat("explosion_force"); if (explosionForce == null) return new ErrorResponse("'explosion_force' is required for explosion force type."); @@ -250,6 +270,9 @@ private static object ApplyExplosionForce(ToolParams p, GameObject go, bool is2D upwards_modifier = upwardsModifier } }; +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } private static GameObject FindTarget(JToken targetToken, string searchMethod) diff --git a/MCPForUnity/Editor/Tools/Physics/PhysicsMaterialOps.cs b/MCPForUnity/Editor/Tools/Physics/PhysicsMaterialOps.cs index d23ec9046..1b2486b8a 100644 --- a/MCPForUnity/Editor/Tools/Physics/PhysicsMaterialOps.cs +++ b/MCPForUnity/Editor/Tools/Physics/PhysicsMaterialOps.cs @@ -27,12 +27,20 @@ public static object Create(JObject @params) return new ErrorResponse(folderError); if (dimension == "2d") +#if MCP_HAS_PHYSICS_2D return Create2D(name, folder, p); +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif if (dimension != "3d") return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'."); +#if MCP_HAS_PHYSICS return Create3D(name, folder, p); +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } public static object Configure(JObject @params) @@ -56,9 +64,17 @@ public static object Configure(JObject @params) return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'."); if (dimension == "2d") +#if MCP_HAS_PHYSICS_2D return Configure2D(path, properties); +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif +#if MCP_HAS_PHYSICS return Configure3D(path, properties); +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } public static object Assign(JObject @params) @@ -86,16 +102,25 @@ public static object Assign(JObject @params) return new ErrorResponse($"GameObject not found: '{targetToken}'."); // Try to load as 3D physics material first +#if MCP_HAS_PHYSICS #if UNITY_6000_0_OR_NEWER var mat3D = AssetDatabase.LoadAssetAtPath(materialPath); #else var mat3D = AssetDatabase.LoadAssetAtPath(materialPath); #endif +#else + UnityEngine.Object mat3D = null; +#endif +#if MCP_HAS_PHYSICS_2D var mat2D = AssetDatabase.LoadAssetAtPath(materialPath); +#else + UnityEngine.Object mat2D = null; +#endif if (mat3D == null && mat2D == null) return new ErrorResponse($"No physics material found at path: '{materialPath}'."); +#if MCP_HAS_PHYSICS // Try 3D colliders first if (mat3D != null) { @@ -131,7 +156,9 @@ public static object Assign(JObject @params) } } } +#endif +#if MCP_HAS_PHYSICS_2D // Try 2D colliders if (mat2D != null) { @@ -167,6 +194,7 @@ public static object Assign(JObject @params) } } } +#endif return new ErrorResponse($"No suitable collider found on '{go.name}'."); } @@ -175,6 +203,7 @@ public static object Assign(JObject @params) // Create helpers // ===================================================================== +#if MCP_HAS_PHYSICS private static object Create3D(string name, string folder, ToolParams p) { float dynamicFriction = p.GetFloat("dynamic_friction") ?? 0.6f; @@ -239,7 +268,9 @@ private static object Create3D(string name, string folder, ToolParams p) } }; } +#endif +#if MCP_HAS_PHYSICS_2D private static object Create2D(string name, string folder, ToolParams p) { float friction = p.GetFloat("friction") ?? 0.4f; @@ -272,6 +303,7 @@ private static object Create2D(string name, string folder, ToolParams p) } }; } +#endif // ===================================================================== // Configure helpers @@ -282,6 +314,7 @@ private static object Create2D(string name, string folder, ToolParams p) "dynamicfriction", "staticfriction", "bounciness", "frictioncombine", "bouncecombine" }; +#if MCP_HAS_PHYSICS private static object Configure3D(string path, JObject properties) { // Validate all keys before applying any changes @@ -367,12 +400,14 @@ private static object Configure3D(string path, JObject properties) data = new { path, changed } }; } +#endif private static readonly HashSet Valid2DMatKeys = new HashSet { "friction", "bounciness" }; +#if MCP_HAS_PHYSICS_2D private static object Configure2D(string path, JObject properties) { // Validate all keys before applying any changes @@ -420,11 +455,13 @@ private static object Configure2D(string path, JObject properties) data = new { path, changed } }; } +#endif // ===================================================================== // Assign helpers // ===================================================================== +#if MCP_HAS_PHYSICS private static Collider FindCollider3D(GameObject go, string colliderType, int? index = null) { if (!string.IsNullOrEmpty(colliderType)) @@ -454,7 +491,9 @@ private static Collider FindCollider3D(GameObject go, string colliderType, int? return go.GetComponent(); } +#endif +#if MCP_HAS_PHYSICS_2D private static Collider2D FindCollider2D(GameObject go, string colliderType, int? index = null) { if (!string.IsNullOrEmpty(colliderType)) @@ -484,6 +523,7 @@ private static Collider2D FindCollider2D(GameObject go, string colliderType, int return go.GetComponent(); } +#endif // ===================================================================== // Folder helpers diff --git a/MCPForUnity/Editor/Tools/Physics/PhysicsQueryOps.cs b/MCPForUnity/Editor/Tools/Physics/PhysicsQueryOps.cs index 9c24d5377..ec2e37200 100644 --- a/MCPForUnity/Editor/Tools/Physics/PhysicsQueryOps.cs +++ b/MCPForUnity/Editor/Tools/Physics/PhysicsQueryOps.cs @@ -25,14 +25,25 @@ public static object Raycast(JObject @params) float maxDistance = p.GetFloat("max_distance") ?? Mathf.Infinity; if (dimension == "2d") + { +#if MCP_HAS_PHYSICS_2D return Raycast2D(originArr, dirArr, maxDistance, p); +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif + } if (dimension != "3d") return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'."); +#if MCP_HAS_PHYSICS return Raycast3D(originArr, dirArr, maxDistance, p); +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } +#if MCP_HAS_PHYSICS private static object Raycast3D(JArray originArr, JArray dirArr, float maxDistance, ToolParams p) { if (originArr.Count < 3) @@ -85,7 +96,9 @@ private static object Raycast3D(JArray originArr, JArray dirArr, float maxDistan } }; } +#endif +#if MCP_HAS_PHYSICS_2D private static object Raycast2D(JArray originArr, JArray dirArr, float maxDistance, ToolParams p) { if (originArr.Count < 2) @@ -128,6 +141,7 @@ private static object Raycast2D(JArray originArr, JArray dirArr, float maxDistan } }; } +#endif public static object Overlap(JObject @params) { @@ -148,14 +162,25 @@ public static object Overlap(JObject @params) int layerMask = ResolveLayerMask(p.Get("layer_mask")); if (dimension == "2d") + { +#if MCP_HAS_PHYSICS_2D return Overlap2D(shape, posArr, sizeToken, layerMask); +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif + } if (dimension != "3d") return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'."); +#if MCP_HAS_PHYSICS return Overlap3D(shape, posArr, sizeToken, layerMask); +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } +#if MCP_HAS_PHYSICS private static object Overlap3D(string shape, JArray posArr, JToken sizeToken, int layerMask) { if (posArr.Count < 3) @@ -227,7 +252,9 @@ private static object Overlap3D(string shape, JArray posArr, JToken sizeToken, i return FormatOverlapResults(results, "3D"); } +#endif +#if MCP_HAS_PHYSICS_2D private static object Overlap2D(string shape, JArray posArr, JToken sizeToken, int layerMask) { if (posArr.Count < 2) @@ -281,7 +308,9 @@ private static object Overlap2D(string shape, JArray posArr, JToken sizeToken, i return FormatOverlapResults2D(results, "2D"); } +#endif +#if MCP_HAS_PHYSICS private static object FormatOverlapResults(Collider[] results, string dimension) { var colliders = new List(); @@ -302,7 +331,9 @@ private static object FormatOverlapResults(Collider[] results, string dimension) data = new { colliders } }; } +#endif +#if MCP_HAS_PHYSICS_2D private static object FormatOverlapResults2D(Collider2D[] results, string dimension) { var colliders = new List(); @@ -323,6 +354,7 @@ private static object FormatOverlapResults2D(Collider2D[] results, string dimens data = new { colliders } }; } +#endif public static object Shapecast(JObject @params) { @@ -343,14 +375,25 @@ public static object Shapecast(JObject @params) float maxDistance = p.GetFloat("max_distance") ?? Mathf.Infinity; if (dimension == "2d") + { +#if MCP_HAS_PHYSICS_2D return Shapecast2D(shape, originArr, dirArr, maxDistance, p); +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif + } if (dimension != "3d") return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'."); +#if MCP_HAS_PHYSICS return Shapecast3D(shape, originArr, dirArr, maxDistance, p); +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } +#if MCP_HAS_PHYSICS private static object Shapecast3D(string shape, JArray originArr, JArray dirArr, float maxDistance, ToolParams p) { if (originArr.Count < 3) @@ -470,7 +513,9 @@ private static object Shapecast3D(string shape, JArray originArr, JArray dirArr, } }; } +#endif +#if MCP_HAS_PHYSICS_2D private static object Shapecast2D(string shape, JArray originArr, JArray dirArr, float maxDistance, ToolParams p) { if (originArr.Count < 2) @@ -559,6 +604,7 @@ private static object Shapecast2D(string shape, JArray originArr, JArray dirArr, } }; } +#endif public static object RaycastAll(JObject @params) { @@ -576,14 +622,25 @@ public static object RaycastAll(JObject @params) float maxDistance = p.GetFloat("max_distance") ?? Mathf.Infinity; if (dimension == "2d") + { +#if MCP_HAS_PHYSICS_2D return RaycastAll2D(originArr, dirArr, maxDistance, p); +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif + } if (dimension != "3d") return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'."); +#if MCP_HAS_PHYSICS return RaycastAll3D(originArr, dirArr, maxDistance, p); +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } +#if MCP_HAS_PHYSICS private static object RaycastAll3D(JArray originArr, JArray dirArr, float maxDistance, ToolParams p) { if (originArr.Count < 3) @@ -632,7 +689,9 @@ private static object RaycastAll3D(JArray originArr, JArray dirArr, float maxDis data = new { hit_count = hits.Length, hits = hitsArray } }; } +#endif +#if MCP_HAS_PHYSICS_2D private static object RaycastAll2D(JArray originArr, JArray dirArr, float maxDistance, ToolParams p) { if (originArr.Count < 2) @@ -670,6 +729,7 @@ private static object RaycastAll2D(JArray originArr, JArray dirArr, float maxDis data = new { hit_count = hits.Length, hits = hitsArray } }; } +#endif public static object Linecast(JObject @params) { @@ -685,14 +745,25 @@ public static object Linecast(JObject @params) return new ErrorResponse("'end' parameter is required (array of floats)."); if (dimension == "2d") + { +#if MCP_HAS_PHYSICS_2D return Linecast2D(startArr, endArr, p); +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif + } if (dimension != "3d") return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'."); +#if MCP_HAS_PHYSICS return Linecast3D(startArr, endArr, p); +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } +#if MCP_HAS_PHYSICS private static object Linecast3D(JArray startArr, JArray endArr, ToolParams p) { if (startArr.Count < 3) @@ -745,7 +816,9 @@ private static object Linecast3D(JArray startArr, JArray endArr, ToolParams p) } }; } +#endif +#if MCP_HAS_PHYSICS_2D private static object Linecast2D(JArray startArr, JArray endArr, ToolParams p) { if (startArr.Count < 2) @@ -788,6 +861,7 @@ private static object Linecast2D(JArray startArr, JArray endArr, ToolParams p) } }; } +#endif private static int ResolveLayerMask(string layerMaskStr) { diff --git a/MCPForUnity/Editor/Tools/Physics/PhysicsRigidbodyOps.cs b/MCPForUnity/Editor/Tools/Physics/PhysicsRigidbodyOps.cs index 1c2daa034..f05974c47 100644 --- a/MCPForUnity/Editor/Tools/Physics/PhysicsRigidbodyOps.cs +++ b/MCPForUnity/Editor/Tools/Physics/PhysicsRigidbodyOps.cs @@ -37,8 +37,16 @@ public static object GetRigidbody(JObject @params) if (go == null) return new ErrorResponse($"GameObject not found: '{targetToken}'."); +#if MCP_HAS_PHYSICS bool has3D = go.GetComponent() != null; +#else + bool has3D = false; +#endif +#if MCP_HAS_PHYSICS_2D bool has2D = go.GetComponent() != null; +#else + bool has2D = false; +#endif if (!has3D && !has2D) return new ErrorResponse($"No Rigidbody or Rigidbody2D found on '{go.name}'."); @@ -52,11 +60,22 @@ public static object GetRigidbody(JObject @params) is2D = has2D && !has3D; if (is2D) + { +#if MCP_HAS_PHYSICS_2D return GetRigidbody2D(go); +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif + } +#if MCP_HAS_PHYSICS return GetRigidbody3D(go); +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } +#if MCP_HAS_PHYSICS private static object GetRigidbody3D(GameObject go) { var rb = go.GetComponent(); @@ -104,7 +123,9 @@ private static object GetRigidbody3D(GameObject go) data }; } +#endif +#if MCP_HAS_PHYSICS_2D private static object GetRigidbody2D(GameObject go) { var rb2d = go.GetComponent(); @@ -149,6 +170,7 @@ private static object GetRigidbody2D(GameObject go) data }; } +#endif public static object ConfigureRigidbody(JObject @params) { @@ -169,8 +191,16 @@ public static object ConfigureRigidbody(JObject @params) if (go == null) return new ErrorResponse($"GameObject not found: '{targetToken}'."); +#if MCP_HAS_PHYSICS bool has3D = go.GetComponent() != null; +#else + bool has3D = false; +#endif +#if MCP_HAS_PHYSICS_2D bool has2D = go.GetComponent() != null; +#else + bool has2D = false; +#endif bool is2D; if (dimensionParam == "2d") @@ -181,11 +211,22 @@ public static object ConfigureRigidbody(JObject @params) is2D = has2D && !has3D; if (is2D) + { +#if MCP_HAS_PHYSICS_2D return ConfigureRigidbody2D(go, properties); +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif + } +#if MCP_HAS_PHYSICS return ConfigureRigidbody3D(go, properties); +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } +#if MCP_HAS_PHYSICS private static object ConfigureRigidbody3D(GameObject go, JObject properties) { var rb = go.GetComponent(); @@ -309,7 +350,9 @@ private static object ConfigureRigidbody3D(GameObject go, JObject properties) data = new { target = go.name, dimension = "3d", changed } }; } +#endif +#if MCP_HAS_PHYSICS_2D private static object ConfigureRigidbody2D(GameObject go, JObject properties) { var rb2d = go.GetComponent(); @@ -433,5 +476,6 @@ private static object ConfigureRigidbody2D(GameObject go, JObject properties) data = new { target = go.name, dimension = "2d", changed } }; } +#endif } } diff --git a/MCPForUnity/Editor/Tools/Physics/PhysicsSettingsOps.cs b/MCPForUnity/Editor/Tools/Physics/PhysicsSettingsOps.cs index 52af8145d..ec26752d2 100644 --- a/MCPForUnity/Editor/Tools/Physics/PhysicsSettingsOps.cs +++ b/MCPForUnity/Editor/Tools/Physics/PhysicsSettingsOps.cs @@ -11,26 +11,34 @@ internal static class PhysicsSettingsOps { public static object Ping(JObject @params) { + var simMode = UnityPhysicsCompat.GetPhysicsSimulationMode().ToString(); + + var data = new Dictionary + { + ["simulationMode"] = simMode + }; + +#if MCP_HAS_PHYSICS var gravity3d = UnityEngine.Physics.gravity; + data["gravity3d"] = new[] { gravity3d.x, gravity3d.y, gravity3d.z }; + data["defaultSolverIterations"] = UnityEngine.Physics.defaultSolverIterations; + data["defaultSolverVelocityIterations"] = UnityEngine.Physics.defaultSolverVelocityIterations; + data["bounceThreshold"] = UnityEngine.Physics.bounceThreshold; + data["sleepThreshold"] = UnityEngine.Physics.sleepThreshold; + data["defaultContactOffset"] = UnityEngine.Physics.defaultContactOffset; + data["queriesHitTriggers"] = UnityEngine.Physics.queriesHitTriggers; +#endif + +#if MCP_HAS_PHYSICS_2D var gravity2d = Physics2D.gravity; - var simMode = UnityPhysicsCompat.GetPhysicsSimulationMode().ToString(); + data["gravity2d"] = new[] { gravity2d.x, gravity2d.y }; +#endif return new { success = true, message = "Physics tool ready.", - data = new - { - gravity3d = new[] { gravity3d.x, gravity3d.y, gravity3d.z }, - gravity2d = new[] { gravity2d.x, gravity2d.y }, - simulationMode = simMode, - defaultSolverIterations = UnityEngine.Physics.defaultSolverIterations, - defaultSolverVelocityIterations = UnityEngine.Physics.defaultSolverVelocityIterations, - bounceThreshold = UnityEngine.Physics.bounceThreshold, - sleepThreshold = UnityEngine.Physics.sleepThreshold, - defaultContactOffset = UnityEngine.Physics.defaultContactOffset, - queriesHitTriggers = UnityEngine.Physics.queriesHitTriggers - } + data = data }; } @@ -41,6 +49,7 @@ public static object GetSettings(JObject @params) if (dimension == "2d") { +#if MCP_HAS_PHYSICS_2D var g = Physics2D.gravity; return new { @@ -58,11 +67,15 @@ public static object GetSettings(JObject @params) autoSyncTransforms = UnityPhysicsCompat.GetPhysics2DAutoSyncTransforms() } }; +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif } if (dimension != "3d") return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'."); +#if MCP_HAS_PHYSICS var g3 = UnityEngine.Physics.gravity; var simMode = UnityPhysicsCompat.GetPhysicsSimulationMode().ToString(); @@ -86,6 +99,9 @@ public static object GetSettings(JObject @params) autoSyncTransforms = UnityPhysicsCompat.GetPhysicsAutoSyncTransforms() } }; +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } public static object SetSettings(JObject @params) @@ -117,6 +133,7 @@ public static object SetSettings(JObject @params) private static object SetSettings3D(JObject settings) { +#if MCP_HAS_PHYSICS // Validate all keys before applying any changes var unknown = new List(); foreach (var prop in settings.Properties()) @@ -211,6 +228,9 @@ private static object SetSettings3D(JObject settings) message = $"Updated {changed.Count} physics 3D setting(s).", data = new { changed } }; +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } private static readonly HashSet Valid2DKeys = new HashSet @@ -222,6 +242,7 @@ private static object SetSettings3D(JObject settings) private static object SetSettings2D(JObject settings) { +#if MCP_HAS_PHYSICS_2D // Validate all keys before applying any changes var unknown = new List(); foreach (var prop in settings.Properties()) @@ -287,6 +308,9 @@ private static object SetSettings2D(JObject settings) message = $"Updated {changed.Count} physics 2D setting(s).", data = new { changed } }; +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif } private static void MarkDynamicsManagerDirty() diff --git a/MCPForUnity/Editor/Tools/Physics/PhysicsSimulationOps.cs b/MCPForUnity/Editor/Tools/Physics/PhysicsSimulationOps.cs index fc922110f..05a490d1a 100644 --- a/MCPForUnity/Editor/Tools/Physics/PhysicsSimulationOps.cs +++ b/MCPForUnity/Editor/Tools/Physics/PhysicsSimulationOps.cs @@ -22,12 +22,17 @@ public static object SimulateStep(JObject @params) if (dimension == "2d") { +#if MCP_HAS_PHYSICS_2D Physics2D.SyncTransforms(); for (int i = 0; i < steps; i++) Physics2D.Simulate(stepSize); +#else + return new ErrorResponse("Physics 2D module (com.unity.modules.physics2d) is not installed."); +#endif } else { +#if MCP_HAS_PHYSICS UnityEngine.Physics.SyncTransforms(); var prevMode = UnityPhysicsCompat.GetPhysicsSimulationMode(); if (prevMode != UnityPhysicsCompat.SimulationMode.Script) @@ -45,6 +50,9 @@ public static object SimulateStep(JObject @params) UnityPhysicsCompat.TrySetPhysicsSimulationMode(prevMode); } } +#else + return new ErrorResponse("Physics module (com.unity.modules.physics) is not installed."); +#endif } // Collect rigidbody states after simulation @@ -81,6 +89,7 @@ private static List CollectTargetRigidbody(string targetStr, string sear if (dimension == "2d") { +#if MCP_HAS_PHYSICS_2D var rb2d = go.GetComponent(); if (rb2d != null) { @@ -97,9 +106,11 @@ private static List CollectTargetRigidbody(string targetStr, string sear angularVelocity = rb2d.angularVelocity }); } +#endif } else { +#if MCP_HAS_PHYSICS var rb = go.GetComponent(); if (rb != null) { @@ -116,6 +127,7 @@ private static List CollectTargetRigidbody(string targetStr, string sear angularVelocity = new[] { rb.angularVelocity.x, rb.angularVelocity.y, rb.angularVelocity.z } }); } +#endif } return results; @@ -128,6 +140,7 @@ private static List CollectActiveRigidbodies(string dimension) if (dimension == "2d") { +#if MCP_HAS_PHYSICS_2D var allRb2d = UnityFindObjectsCompat.FindAll(); foreach (var rb2d in allRb2d) { @@ -148,9 +161,11 @@ private static List CollectActiveRigidbodies(string dimension) angularVelocity = rb2d.angularVelocity }); } +#endif } else { +#if MCP_HAS_PHYSICS var allRb = UnityFindObjectsCompat.FindAll(); foreach (var rb in allRb) { @@ -171,6 +186,7 @@ private static List CollectActiveRigidbodies(string dimension) angularVelocity = new[] { rb.angularVelocity.x, rb.angularVelocity.y, rb.angularVelocity.z } }); } +#endif } return results; diff --git a/MCPForUnity/Editor/Tools/Physics/PhysicsValidationOps.cs b/MCPForUnity/Editor/Tools/Physics/PhysicsValidationOps.cs index d87808f0b..b4537181c 100644 --- a/MCPForUnity/Editor/Tools/Physics/PhysicsValidationOps.cs +++ b/MCPForUnity/Editor/Tools/Physics/PhysicsValidationOps.cs @@ -57,8 +57,10 @@ public static object Validate(JObject @params) ValidateRecursive(root, dimension, warnings, categoryCounts, ref scanned); } +#if MCP_HAS_PHYSICS if (dimension != "2d") CheckCollisionMatrix(warnings, categoryCounts); +#endif } int totalWarnings = warnings.Count; @@ -106,6 +108,7 @@ private static void ValidateGameObject(GameObject go, string dimension, List(); @@ -122,8 +125,10 @@ private static void ValidateGameObject(GameObject go, string dimension, List(); @@ -143,7 +148,9 @@ private static void ValidateGameObject(GameObject go, string dimension, List(); @@ -163,11 +170,20 @@ private static void ValidateGameObject(GameObject go, string dimension, List() != null || go.GetComponent() != null; +#elif MCP_HAS_PHYSICS + bool hasCollider = go.GetComponent() != null; +#elif MCP_HAS_PHYSICS_2D + bool hasCollider = go.GetComponent() != null; +#else + bool hasCollider = false; +#endif if (hasCollider) { bool nonUniform = Mathf.Abs(scale.x - scale.y) > 0.01f @@ -182,6 +198,7 @@ private static void ValidateGameObject(GameObject go, string dimension, List(); @@ -196,8 +213,10 @@ private static void ValidateGameObject(GameObject go, string dimension, List()) @@ -210,7 +229,9 @@ private static void ValidateGameObject(GameObject go, string dimension, List()) @@ -223,8 +244,10 @@ private static void ValidateGameObject(GameObject go, string dimension, List() != null || go.GetComponent() != null; @@ -246,6 +269,7 @@ private static void ValidateGameObject(GameObject go, string dimension, List warnings, Dictionary categoryCounts) { @@ -292,6 +317,7 @@ private static void CheckCollisionMatrix(List warnings, Dictionary GetAllRootGameObjects() { diff --git a/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs b/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs index a49346ade..558978b7d 100644 --- a/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs +++ b/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs @@ -76,10 +76,21 @@ public static ScreenshotCaptureResult CaptureToProjectFolder( bool ensureUniqueFileName = true, string folderOverride = null) { +#if MCP_HAS_SCREEN_CAPTURE ScreenshotCaptureResult result = PrepareCaptureResult(fileName, superSize, ensureUniqueFileName, folderOverride, isAsync: true); // ScreenCapture.CaptureScreenshot accepts paths relative to the project root. ScreenCapture.CaptureScreenshot(result.ProjectRelativePath, result.SuperSize); return result; +#else + // Screen Capture module (com.unity.modules.screencapture) not installed — + // fall back to camera-based capture. + var cam = FindAvailableCamera(); + if (cam != null) + return CaptureFromCameraToProjectFolder(cam, fileName, superSize, ensureUniqueFileName, folderOverride: folderOverride); + throw new InvalidOperationException( + "Cannot capture screenshot: the Screen Capture module is not installed and no Camera was found. " + + "Enable it via Window > Package Manager > Built-in > Screen Capture, or add a Camera to the scene."); +#endif } /// @@ -166,7 +177,7 @@ public static ScreenshotCaptureResult CaptureFromCameraToProjectFolder( return result; } -#if UNITY_EDITOR +#if UNITY_EDITOR && MCP_HAS_SCREEN_CAPTURE // Synchronously drive a WaitForEndOfFrame ScreenshotCapturer by pumping the editor's // player loop. Play-mode only; EditorApplication.Step is a no-op in edit mode. private static Texture2D CaptureCompositedAfterFrame(int superSize, int timeoutSteps = 5) @@ -215,6 +226,7 @@ public static ScreenshotCaptureResult CaptureComposited( int imgW = 0, imgH = 0; try { +#if MCP_HAS_SCREEN_CAPTURE #if UNITY_EDITOR // In play mode, inline ScreenCapture reads a backbuffer before UITK has // composited; route through WaitForEndOfFrame instead. @@ -223,10 +235,11 @@ public static ScreenshotCaptureResult CaptureComposited( : ScreenCapture.CaptureScreenshotAsTexture(result.SuperSize); #else tex = ScreenCapture.CaptureScreenshotAsTexture(result.SuperSize); +#endif #endif if (tex == null) { - // Fallback to camera-based if ScreenCapture fails + // Fallback to camera-based if ScreenCapture fails (or module not installed) var cam = FindAvailableCamera(); if (cam != null) return CaptureFromCameraToProjectFolder(cam, fileName, superSize, ensureUniqueFileName, @@ -762,8 +775,10 @@ private System.Collections.IEnumerator Start() { yield return new WaitForEndOfFrame(); Texture2D tex = null; +#if MCP_HAS_SCREEN_CAPTURE try { tex = ScreenCapture.CaptureScreenshotAsTexture(_superSize); } catch (Exception ex) { Debug.LogError($"[MCP for Unity] CaptureScreenshotAsTexture failed: {ex.Message}"); } +#endif _onComplete?.Invoke(tex); Destroy(gameObject); } diff --git a/MCPForUnity/Runtime/Helpers/UnityPhysicsCompat.cs b/MCPForUnity/Runtime/Helpers/UnityPhysicsCompat.cs index fe3158514..c840fa807 100644 --- a/MCPForUnity/Runtime/Helpers/UnityPhysicsCompat.cs +++ b/MCPForUnity/Runtime/Helpers/UnityPhysicsCompat.cs @@ -1,6 +1,5 @@ using System; using System.Reflection; -using UnityEngine; namespace MCPForUnity.Runtime.Helpers { @@ -34,6 +33,16 @@ public enum SimulationMode Unknown, } + // Resolve the engine physics types by name so this shim compiles even when the + // Physics / Physics2D built-in modules are removed from the project. When a type + // is null the probes below return null/Unknown, matching the obsolete-property path. + private static readonly Type PhysicsType = + Type.GetType("UnityEngine.Physics, UnityEngine.PhysicsModule") + ?? Type.GetType("UnityEngine.Physics, UnityEngine.CoreModule"); + private static readonly Type Physics2DType = + Type.GetType("UnityEngine.Physics2D, UnityEngine.Physics2DModule") + ?? Type.GetType("UnityEngine.Physics2D, UnityEngine.CoreModule"); + // ---------- Physics2D ---------- private static PropertyInfo _physics2DAutoSync; @@ -46,7 +55,7 @@ private static PropertyInfo Physics2DAutoSyncProp if (!_physics2DProbed) { _physics2DProbed = true; - _physics2DAutoSync = typeof(Physics2D).GetProperty( + _physics2DAutoSync = Physics2DType?.GetProperty( "autoSyncTransforms", BindingFlags.Public | BindingFlags.Static); } @@ -98,7 +107,7 @@ private static PropertyInfo PhysicsAutoSyncProp if (!_physicsProbed) { _physicsProbed = true; - _physicsAutoSync = typeof(Physics).GetProperty( + _physicsAutoSync = PhysicsType?.GetProperty( "autoSyncTransforms", BindingFlags.Public | BindingFlags.Static); } @@ -154,7 +163,7 @@ private static PropertyInfo PhysicsSimulationModeProp if (!_physicsSimulationModeProbed) { _physicsSimulationModeProbed = true; - _physicsSimulationMode = typeof(Physics).GetProperty( + _physicsSimulationMode = PhysicsType?.GetProperty( "simulationMode", BindingFlags.Public | BindingFlags.Static); } @@ -169,7 +178,7 @@ private static PropertyInfo PhysicsAutoSimulationProp if (!_physicsAutoSimulationProbed) { _physicsAutoSimulationProbed = true; - _physicsAutoSimulation = typeof(Physics).GetProperty( + _physicsAutoSimulation = PhysicsType?.GetProperty( "autoSimulation", BindingFlags.Public | BindingFlags.Static); } diff --git a/MCPForUnity/Runtime/MCPForUnity.Runtime.asmdef b/MCPForUnity/Runtime/MCPForUnity.Runtime.asmdef index c5c19611b..0dca4a84b 100644 --- a/MCPForUnity/Runtime/MCPForUnity.Runtime.asmdef +++ b/MCPForUnity/Runtime/MCPForUnity.Runtime.asmdef @@ -9,6 +9,22 @@ "precompiledReferences": [], "autoReferenced": true, "defineConstraints": [], - "versionDefines": [], + "versionDefines": [ + { + "name": "com.unity.modules.physics", + "expression": "1.0.0", + "define": "MCP_HAS_PHYSICS" + }, + { + "name": "com.unity.modules.physics2d", + "expression": "1.0.0", + "define": "MCP_HAS_PHYSICS_2D" + }, + { + "name": "com.unity.modules.screencapture", + "expression": "1.0.0", + "define": "MCP_HAS_SCREEN_CAPTURE" + } + ], "noEngineReferences": false } \ No newline at end of file From 6b1eb3bd6906d480aeca36353a8b20e3a7309fe3 Mon Sep 17 00:00:00 2001 From: Ark Tarusov Date: Tue, 16 Jun 2026 21:00:17 +0200 Subject: [PATCH 2/4] stop forcing physics and screen capture modules as package dependencies --- MCPForUnity/package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/MCPForUnity/package.json b/MCPForUnity/package.json index e6ece4f95..e93179b0f 100644 --- a/MCPForUnity/package.json +++ b/MCPForUnity/package.json @@ -9,10 +9,7 @@ "dependencies": { "com.unity.modules.animation": "1.0.0", "com.unity.modules.imageconversion": "1.0.0", - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.physics2d": "1.0.0", "com.unity.modules.uielements": "1.0.0", - "com.unity.modules.screencapture": "1.0.0", "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.nuget.newtonsoft-json": "3.0.2", "com.unity.test-framework": "1.1.31" From 0878bba5a7472be04181f05bca561f7be1c23a33 Mon Sep 17 00:00:00 2001 From: Ark Tarusov Date: Thu, 18 Jun 2026 17:23:40 +0200 Subject: [PATCH 3/4] handle missing screen capture module in the play-mode ui capture path --- MCPForUnity/Editor/Tools/ManageUI.cs | 11 ++++++++++- MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs | 6 ++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/MCPForUnity/Editor/Tools/ManageUI.cs b/MCPForUnity/Editor/Tools/ManageUI.cs index cad174139..cc3cd8b5a 100644 --- a/MCPForUnity/Editor/Tools/ManageUI.cs +++ b/MCPForUnity/Editor/Tools/ManageUI.cs @@ -811,10 +811,12 @@ private static object SerializeVisualElement(VisualElement element, int depth, i // Play-mode coroutine capture state. Only one capture is in-flight at a // time; concurrent render_ui calls while a capture is pending are rejected - // with an explicit error. + // with an explicit error. Only used when the Screen Capture module is present. +#if MCP_HAS_SCREEN_CAPTURE private static Texture2D s_pendingCaptureTex; private static bool s_pendingCaptureDone; private static bool s_pendingCaptureStarted; +#endif private static object RenderUI(JObject @params) { @@ -854,6 +856,7 @@ private static object RenderUI(JObject @params) // Second call: result is ready – save PNG and return data. if (Application.isPlaying) { +#if MCP_HAS_SCREEN_CAPTURE // Build the output paths (used by both the pending and ready branches) string resolvedPlayName = string.IsNullOrWhiteSpace(fileName) ? $"ui-render-{DateTime.Now:yyyyMMdd-HHmmss}.png" @@ -954,6 +957,12 @@ private static object RenderUI(JObject @params) { "gameObject", (object)target ?? uxmlPath }, { "note", "A screen capture was scheduled for the end of this frame. Call render_ui once more to get the result." } }); +#else + return new ErrorResponse( + "Play-mode UI capture requires the Screen Capture module (com.unity.modules.screencapture), " + + "which is not installed. Enable it via Window > Package Manager > Built-in > Screen Capture, " + + "or render the UI outside Play mode (which captures via a RenderTexture instead)."); +#endif } // ── End play-mode branch ──────────────────────────────────────────────── diff --git a/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs b/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs index 558978b7d..6befa62e0 100644 --- a/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs +++ b/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs @@ -753,9 +753,12 @@ private static string GetProjectRootPath() } } +#if MCP_HAS_SCREEN_CAPTURE /// /// Transient MonoBehaviour that yields WaitForEndOfFrame, calls /// ScreenCapture.CaptureScreenshotAsTexture, invokes the callback, and self-destructs. + /// Only compiled when the Screen Capture module is present; callers must gate their use + /// behind MCP_HAS_SCREEN_CAPTURE and fall back otherwise. /// public sealed class ScreenshotCapturer : MonoBehaviour { @@ -775,12 +778,11 @@ private System.Collections.IEnumerator Start() { yield return new WaitForEndOfFrame(); Texture2D tex = null; -#if MCP_HAS_SCREEN_CAPTURE try { tex = ScreenCapture.CaptureScreenshotAsTexture(_superSize); } catch (Exception ex) { Debug.LogError($"[MCP for Unity] CaptureScreenshotAsTexture failed: {ex.Message}"); } -#endif _onComplete?.Invoke(tex); Destroy(gameObject); } } +#endif } From d30bbc4c51e99cf3c10ac9209a447b200bb89d0a Mon Sep 17 00:00:00 2001 From: Ark Tarusov Date: Thu, 18 Jun 2026 21:12:58 +0200 Subject: [PATCH 4/4] document built-in module requirements for physics and screen capture tools --- Server/src/services/tools/manage_camera.py | 3 +++ Server/src/services/tools/manage_physics.py | 11 +++++++++-- Server/src/services/tools/manage_ui.py | 4 ++++ website/docs/reference/tools/core/manage_camera.md | 2 +- website/docs/reference/tools/core/manage_physics.md | 4 +++- website/docs/reference/tools/ui/manage_ui.md | 4 ++++ 6 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Server/src/services/tools/manage_camera.py b/Server/src/services/tools/manage_camera.py index ea329259c..4a2e5d155 100644 --- a/Server/src/services/tools/manage_camera.py +++ b/Server/src/services/tools/manage_camera.py @@ -63,6 +63,9 @@ "CAPTURE:\n" "- screenshot: Capture a screenshot. By default (no camera specified) uses ScreenCapture API, " "which captures all render layers including Screen Space - Overlay UI canvases. " + "The ScreenCapture path needs Unity's built-in Screen Capture module (com.unity.modules.screencapture), " + "which is optional and NOT forced as a dependency; if it is absent the capture falls back to camera " + "rendering (a Camera must exist in the scene), so Screen Space - Overlay UI will be missing from the image. " "Specifying a camera uses direct camera rendering, which EXCLUDES Screen Space - Overlay canvases " "(use only when you need a specific viewpoint without UI). " "Supports include_image=true for inline base64 PNG, " diff --git a/Server/src/services/tools/manage_physics.py b/Server/src/services/tools/manage_physics.py index 8ffea0624..1cca63121 100644 --- a/Server/src/services/tools/manage_physics.py +++ b/Server/src/services/tools/manage_physics.py @@ -47,14 +47,21 @@ "FORCES: apply_force\n" "RIGIDBODY: get_rigidbody, configure_rigidbody\n" "VALIDATION: validate\n" - "SIMULATION: simulate_step\n" + "SIMULATION: simulate_step\n\n" + "MODULE REQUIREMENTS: 3D actions need Unity's built-in Physics module " + "(com.unity.modules.physics); 2D actions need the Physics 2D module " + "(com.unity.modules.physics2d). These modules are optional and are NOT forced as package " + "dependencies, so a project may have one, both, or neither. 'dimension' defaults to '3d': " + "in a project without the 3D Physics module you MUST pass dimension:'2d' explicitly, otherwise " + "the action returns a 'Physics module is not installed' error. If neither module is installed, " + "every action reports that physics tools are unavailable.\n" ), annotations=ToolAnnotations(title="Manage Physics", destructiveHint=True), ) async def manage_physics( ctx: Context, action: Annotated[PhysicsAction, "The physics action to perform."], - dimension: Annotated[Optional[str], "Physics dimension: '3d' (default) or '2d'."] = None, + dimension: Annotated[Optional[str], "Physics dimension: '3d' (default) or '2d'. The matching built-in physics module must be installed; pass '2d' explicitly in projects that don't have the 3D Physics module."] = None, settings: Annotated[ Optional[dict[str, Any]], "Key-value settings for set_settings." ] = None, diff --git a/Server/src/services/tools/manage_ui.py b/Server/src/services/tools/manage_ui.py index 9186306b3..5c4b27453 100644 --- a/Server/src/services/tools/manage_ui.py +++ b/Server/src/services/tools/manage_ui.py @@ -40,7 +40,11 @@ "8. Use render_ui to capture a visual preview for self-evaluation\n" " - In play mode: first call queues a WaitForEndOfFrame screen capture and returns pending=true;\n" " call render_ui a second time to retrieve the saved PNG (hasContent will be true).\n" + " Requires Unity's built-in Screen Capture module (com.unity.modules.screencapture), which is\n" + " optional and NOT forced as a dependency. If it is absent, play-mode render_ui returns a\n" + " 'Screen Capture module is not installed' error — capture the UI in editor mode instead.\n" " - In editor mode: assigns a RenderTexture to PanelSettings (best-effort; may stay blank).\n" + " Works without the Screen Capture module.\n" "9. Use detach_ui_document to remove UIDocument from a GameObject\n" "10. Use delete to remove .uxml/.uss files\n\n" "Important: Always use (with the ui: namespace prefix) in UXML, not bare