using System.Collections.Generic; using Newtonsoft.Json.Linq; using UnityEditor; using UnityEngine; using MCPForUnity.Editor.Helpers; using MCPForUnity.Runtime.Helpers; namespace MCPForUnity.Editor.Tools.Physics { internal static class PhysicsSettingsOps { public static object Ping(JObject @params) { var gravity3d = UnityEngine.Physics.gravity; var gravity2d = Physics2D.gravity; var simMode = UnityPhysicsCompat.GetPhysicsSimulationMode().ToString(); 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 } }; } public static object GetSettings(JObject @params) { var p = new ToolParams(@params); string dimension = (p.Get("dimension") ?? "3d").ToLowerInvariant(); if (dimension == "2d") { var g = Physics2D.gravity; return new { success = true, message = "Physics2D settings retrieved.", data = new { dimension = "2d", gravity = new[] { g.x, g.y }, velocityIterations = Physics2D.velocityIterations, positionIterations = Physics2D.positionIterations, queriesHitTriggers = Physics2D.queriesHitTriggers, queriesStartInColliders = Physics2D.queriesStartInColliders, callbacksOnDisable = Physics2D.callbacksOnDisable, autoSyncTransforms = UnityPhysicsCompat.GetPhysics2DAutoSyncTransforms() } }; } if (dimension != "3d") return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'."); var g3 = UnityEngine.Physics.gravity; var simMode = UnityPhysicsCompat.GetPhysicsSimulationMode().ToString(); return new { success = true, message = "Physics3D settings retrieved.", data = new { dimension = "3d", gravity = new[] { g3.x, g3.y, g3.z }, defaultContactOffset = UnityEngine.Physics.defaultContactOffset, sleepThreshold = UnityEngine.Physics.sleepThreshold, defaultSolverIterations = UnityEngine.Physics.defaultSolverIterations, defaultSolverVelocityIterations = UnityEngine.Physics.defaultSolverVelocityIterations, bounceThreshold = UnityEngine.Physics.bounceThreshold, defaultMaxAngularSpeed = UnityEngine.Physics.defaultMaxAngularSpeed, queriesHitTriggers = UnityEngine.Physics.queriesHitTriggers, queriesHitBackfaces = UnityEngine.Physics.queriesHitBackfaces, simulationMode = simMode, autoSyncTransforms = UnityPhysicsCompat.GetPhysicsAutoSyncTransforms() } }; } public static object SetSettings(JObject @params) { var p = new ToolParams(@params); string dimension = (p.Get("dimension") ?? "3d").ToLowerInvariant(); var settings = p.GetRaw("settings") as JObject; if (settings == null || settings.Count == 0) return new ErrorResponse("'settings' parameter is required and must be a non-empty object."); if (dimension != "3d" && dimension != "2d") return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'."); if (dimension == "2d") return SetSettings2D(settings); return SetSettings3D(settings); } private static readonly HashSet Valid3DKeys = new HashSet { "gravity", "defaultcontactoffset", "sleepthreshold", "defaultsolveriterations", "defaultsolvervelocityiterations", "bouncethreshold", "defaultmaxangularspeed", "querieshittriggers", "querieshitbackfaces", "simulationmode", "autosynctransforms" }; private static object SetSettings3D(JObject settings) { // Validate all keys before applying any changes var unknown = new List(); foreach (var prop in settings.Properties()) { if (!Valid3DKeys.Contains(prop.Name.ToLowerInvariant())) unknown.Add(prop.Name); } if (unknown.Count > 0) return new ErrorResponse( $"Unknown 3D physics setting(s): {string.Join(", ", unknown)}."); var changed = new List(); foreach (var prop in settings.Properties()) { string key = prop.Name.ToLowerInvariant(); switch (key) { case "gravity": { var arr = prop.Value as JArray; if (arr == null || arr.Count < 3) return new ErrorResponse("3D gravity requires [x, y, z] array."); UnityEngine.Physics.gravity = new Vector3( arr[0].Value(), arr[1].Value(), arr[2].Value()); changed.Add("gravity"); break; } case "defaultcontactoffset": UnityEngine.Physics.defaultContactOffset = prop.Value.Value(); changed.Add("defaultContactOffset"); break; case "sleepthreshold": UnityEngine.Physics.sleepThreshold = prop.Value.Value(); changed.Add("sleepThreshold"); break; case "defaultsolveriterations": UnityEngine.Physics.defaultSolverIterations = prop.Value.Value(); changed.Add("defaultSolverIterations"); break; case "defaultsolvervelocityiterations": UnityEngine.Physics.defaultSolverVelocityIterations = prop.Value.Value(); changed.Add("defaultSolverVelocityIterations"); break; case "bouncethreshold": UnityEngine.Physics.bounceThreshold = prop.Value.Value(); changed.Add("bounceThreshold"); break; case "defaultmaxangularspeed": UnityEngine.Physics.defaultMaxAngularSpeed = prop.Value.Value(); changed.Add("defaultMaxAngularSpeed"); break; case "querieshittriggers": UnityEngine.Physics.queriesHitTriggers = prop.Value.Value(); changed.Add("queriesHitTriggers"); break; case "querieshitbackfaces": UnityEngine.Physics.queriesHitBackfaces = prop.Value.Value(); changed.Add("queriesHitBackfaces"); break; case "simulationmode": { string modeStr = prop.Value.ToString(); if (!System.Enum.TryParse(modeStr, true, out var mode) || mode == UnityPhysicsCompat.SimulationMode.Unknown) { return new ErrorResponse( $"Invalid simulationMode: '{modeStr}'. Valid: FixedUpdate, Update, Script."); } if (!UnityPhysicsCompat.TrySetPhysicsSimulationMode(mode)) { return new ErrorResponse( $"simulationMode '{modeStr}' is not supported on this Unity version."); } changed.Add("simulationMode"); break; } case "autosynctransforms": if (UnityPhysicsCompat.TrySetPhysicsAutoSyncTransforms(prop.Value.Value())) { changed.Add("autoSyncTransforms"); } break; } } MarkDynamicsManagerDirty(); return new { success = true, message = $"Updated {changed.Count} physics 3D setting(s).", data = new { changed } }; } private static readonly HashSet Valid2DKeys = new HashSet { "gravity", "velocityiterations", "positioniterations", "querieshittriggers", "queriesstartincolliders", "callbacksondisable", "autosynctransforms" }; private static object SetSettings2D(JObject settings) { // Validate all keys before applying any changes var unknown = new List(); foreach (var prop in settings.Properties()) { if (!Valid2DKeys.Contains(prop.Name.ToLowerInvariant())) unknown.Add(prop.Name); } if (unknown.Count > 0) return new ErrorResponse( $"Unknown 2D physics setting(s): {string.Join(", ", unknown)}."); var changed = new List(); foreach (var prop in settings.Properties()) { string key = prop.Name.ToLowerInvariant(); switch (key) { case "gravity": { var arr = prop.Value as JArray; if (arr == null || arr.Count < 2) return new ErrorResponse("2D gravity requires [x, y] array."); Physics2D.gravity = new Vector2( arr[0].Value(), arr[1].Value()); changed.Add("gravity"); break; } case "velocityiterations": Physics2D.velocityIterations = prop.Value.Value(); changed.Add("velocityIterations"); break; case "positioniterations": Physics2D.positionIterations = prop.Value.Value(); changed.Add("positionIterations"); break; case "querieshittriggers": Physics2D.queriesHitTriggers = prop.Value.Value(); changed.Add("queriesHitTriggers"); break; case "queriesstartincolliders": Physics2D.queriesStartInColliders = prop.Value.Value(); changed.Add("queriesStartInColliders"); break; case "callbacksondisable": Physics2D.callbacksOnDisable = prop.Value.Value(); changed.Add("callbacksOnDisable"); break; case "autosynctransforms": if (UnityPhysicsCompat.TrySetPhysics2DAutoSyncTransforms(prop.Value.Value())) { changed.Add("autoSyncTransforms"); } break; } } MarkPhysics2DSettingsDirty(); return new { success = true, message = $"Updated {changed.Count} physics 2D setting(s).", data = new { changed } }; } private static void MarkDynamicsManagerDirty() { var assets = AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/DynamicsManager.asset"); if (assets != null && assets.Length > 0) EditorUtility.SetDirty(assets[0]); } private static void MarkPhysics2DSettingsDirty() { var assets = AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/Physics2DSettings.asset"); if (assets != null && assets.Length > 0) EditorUtility.SetDirty(assets[0]); } } }