Skip to content

Commit c718aaa

Browse files
committed
Initial upload
1 parent a540d8d commit c718aaa

27 files changed

Lines changed: 2077 additions & 63 deletions

MCPForUnity/Editor/Tools/Physics.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

MCPForUnity/Editor/Tools/Physics/CollisionMatrixOps.cs.meta

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

MCPForUnity/Editor/Tools/Physics/JointOps.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,20 @@ public static object AddJoint(JObject @params)
5050
if (go == null)
5151
return new ErrorResponse($"Target GameObject '{targetStr}' not found.");
5252

53-
bool is2D = go.GetComponent<Rigidbody2D>() != null;
53+
string dimensionParam = p.Get("dimension")?.ToLowerInvariant();
5454
bool is3D = go.GetComponent<Rigidbody>() != null;
55+
bool has2DRb = go.GetComponent<Rigidbody2D>() != null;
56+
bool is2D;
57+
58+
if (dimensionParam == "2d")
59+
is2D = has2DRb;
60+
else if (dimensionParam == "3d")
61+
is2D = false;
62+
else
63+
{
64+
// Auto-detect; if both, prefer 3D (3D is the default physics)
65+
is2D = has2DRb && !is3D;
66+
}
5567

5668
if (!is2D && !is3D)
5769
return new ErrorResponse($"Target '{go.name}' has no Rigidbody or Rigidbody2D. Add one before adding a joint.");
@@ -136,6 +148,14 @@ public static object ConfigureJoint(JObject @params)
136148
{
137149
if (!string.IsNullOrEmpty(jointTypeStr))
138150
return new ErrorResponse($"No joint of type '{jointTypeStr}' found on '{go.name}'.");
151+
152+
// Check if multiple joints exist to give a better error
153+
var joints3D = go.GetComponents<Joint>();
154+
var joints2D = go.GetComponents<Joint2D>();
155+
int total = joints3D.Length + joints2D.Length;
156+
if (total > 1)
157+
return new ErrorResponse($"Multiple joints found on '{go.name}' ({total} total). Specify 'joint_type' to target a specific joint.");
158+
139159
return new ErrorResponse($"No joint found on '{go.name}'.");
140160
}
141161

MCPForUnity/Editor/Tools/Physics/JointOps.cs.meta

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

MCPForUnity/Editor/Tools/Physics/ManagePhysics.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,25 @@ public static object HandleCommand(JObject @params)
5757
// --- Query actions ---
5858
case "raycast":
5959
return PhysicsQueryOps.Raycast(@params);
60+
case "raycast_all":
61+
return PhysicsQueryOps.RaycastAll(@params);
62+
case "linecast":
63+
return PhysicsQueryOps.Linecast(@params);
64+
case "shapecast":
65+
return PhysicsQueryOps.Shapecast(@params);
6066
case "overlap":
6167
return PhysicsQueryOps.Overlap(@params);
6268

69+
// --- Force actions ---
70+
case "apply_force":
71+
return PhysicsForceOps.ApplyForce(@params);
72+
73+
// --- Rigidbody actions ---
74+
case "get_rigidbody":
75+
return PhysicsRigidbodyOps.GetRigidbody(@params);
76+
case "configure_rigidbody":
77+
return PhysicsRigidbodyOps.ConfigureRigidbody(@params);
78+
6379
// --- Validation ---
6480
case "validate":
6581
return PhysicsValidationOps.Validate(@params);
@@ -75,7 +91,8 @@ public static object HandleCommand(JObject @params)
7591
+ "get_collision_matrix, set_collision_matrix, "
7692
+ "create_physics_material, configure_physics_material, assign_physics_material, "
7793
+ "add_joint, configure_joint, remove_joint, "
78-
+ "raycast, overlap, validate, simulate_step.");
94+
+ "raycast, raycast_all, linecast, shapecast, overlap, "
95+
+ "apply_force, get_rigidbody, configure_rigidbody, validate, simulate_step.");
7996
}
8097
}
8198
catch (Exception ex)

MCPForUnity/Editor/Tools/Physics/ManagePhysics.cs.meta

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Newtonsoft.Json.Linq;
4+
using UnityEngine;
5+
using MCPForUnity.Editor.Helpers;
6+
7+
namespace MCPForUnity.Editor.Tools.Physics
8+
{
9+
internal static class PhysicsForceOps
10+
{
11+
public static object ApplyForce(JObject @params)
12+
{
13+
var p = new ToolParams(@params);
14+
15+
var targetResult = p.GetRequired("target");
16+
var errorObj = targetResult.GetOrError(out string targetStr);
17+
if (errorObj != null) return errorObj;
18+
19+
string searchMethod = p.Get("search_method");
20+
21+
GameObject go = FindTarget(@params["target"], searchMethod);
22+
if (go == null)
23+
return new ErrorResponse($"Target GameObject '{targetStr}' not found.");
24+
25+
// Detect dimension
26+
string dimensionParam = p.Get("dimension")?.ToLowerInvariant();
27+
bool has3DRb = go.GetComponent<Rigidbody>() != null;
28+
bool has2DRb = go.GetComponent<Rigidbody2D>() != null;
29+
bool is2D;
30+
31+
if (dimensionParam == "2d")
32+
is2D = true;
33+
else if (dimensionParam == "3d")
34+
is2D = false;
35+
else
36+
is2D = has2DRb && !has3DRb;
37+
38+
// Validate rigidbody exists
39+
if (is2D && !has2DRb)
40+
return new ErrorResponse($"Target '{go.name}' has no Rigidbody2D. Add one before applying force.");
41+
if (!is2D && !has3DRb)
42+
return new ErrorResponse($"Target '{go.name}' has no Rigidbody. Add one before applying force.");
43+
44+
// Validate not kinematic
45+
if (is2D)
46+
{
47+
var rb2d = go.GetComponent<Rigidbody2D>();
48+
if (rb2d.bodyType == RigidbodyType2D.Kinematic)
49+
return new ErrorResponse($"Cannot apply force to kinematic Rigidbody on '{go.name}'.");
50+
}
51+
else
52+
{
53+
var rb = go.GetComponent<Rigidbody>();
54+
if (rb.isKinematic)
55+
return new ErrorResponse($"Cannot apply force to kinematic Rigidbody on '{go.name}'.");
56+
}
57+
58+
string forceType = (p.Get("force_type") ?? "normal").ToLowerInvariant();
59+
60+
if (forceType == "explosion")
61+
return ApplyExplosionForce(p, go, is2D);
62+
63+
if (forceType == "normal")
64+
return ApplyNormalForce(p, go, is2D);
65+
66+
return new ErrorResponse($"Unknown force_type: '{forceType}'. Valid types: normal, explosion.");
67+
}
68+
69+
private static object ApplyNormalForce(ToolParams p, GameObject go, bool is2D)
70+
{
71+
var forceToken = p.GetRaw("force") as JArray;
72+
var torqueToken = p.GetRaw("torque");
73+
74+
if (forceToken == null && torqueToken == null)
75+
return new ErrorResponse("Either 'force' or 'torque' (or both) must be provided.");
76+
77+
string modeStr = p.Get("force_mode");
78+
var positionToken = p.GetRaw("position") as JArray;
79+
80+
var applied = new List<string>();
81+
var responseData = new Dictionary<string, object>
82+
{
83+
["target"] = go.name,
84+
["dimension"] = is2D ? "2d" : "3d",
85+
["force_type"] = "normal"
86+
};
87+
88+
if (is2D)
89+
{
90+
ForceMode2D mode2d = ForceMode2D.Force;
91+
if (!string.IsNullOrEmpty(modeStr))
92+
{
93+
if (!Enum.TryParse<ForceMode2D>(modeStr, true, out mode2d))
94+
return new ErrorResponse($"Invalid ForceMode2D: '{modeStr}'. Valid values: Force, Impulse.");
95+
96+
if (mode2d != ForceMode2D.Force && mode2d != ForceMode2D.Impulse)
97+
return new ErrorResponse($"ForceMode2D only supports Force and Impulse, not '{modeStr}'.");
98+
}
99+
100+
responseData["force_mode"] = mode2d.ToString();
101+
var rb2d = go.GetComponent<Rigidbody2D>();
102+
103+
if (forceToken != null)
104+
{
105+
if (forceToken.Count < 2)
106+
return new ErrorResponse("'force' array must contain at least 2 floats for 2D.");
107+
108+
var forceVec = new Vector2(forceToken[0].Value<float>(), forceToken[1].Value<float>());
109+
110+
if (positionToken != null)
111+
{
112+
if (positionToken.Count < 2)
113+
return new ErrorResponse("'position' array must contain at least 2 floats for 2D.");
114+
115+
var posVec = new Vector2(positionToken[0].Value<float>(), positionToken[1].Value<float>());
116+
rb2d.AddForceAtPosition(forceVec, posVec, mode2d);
117+
}
118+
else
119+
{
120+
rb2d.AddForce(forceVec, mode2d);
121+
}
122+
123+
responseData["force"] = new[] { forceVec.x, forceVec.y };
124+
applied.Add("force");
125+
}
126+
127+
if (torqueToken != null)
128+
{
129+
float torqueFloat = torqueToken.Value<float>();
130+
rb2d.AddTorque(torqueFloat, mode2d);
131+
responseData["torque"] = torqueFloat;
132+
applied.Add("torque");
133+
}
134+
}
135+
else
136+
{
137+
ForceMode mode = ForceMode.Force;
138+
if (!string.IsNullOrEmpty(modeStr))
139+
{
140+
if (!Enum.TryParse<ForceMode>(modeStr, true, out mode))
141+
return new ErrorResponse($"Invalid ForceMode: '{modeStr}'. Valid values: Force, Impulse, Acceleration, VelocityChange.");
142+
}
143+
144+
responseData["force_mode"] = mode.ToString();
145+
var rb = go.GetComponent<Rigidbody>();
146+
147+
if (forceToken != null)
148+
{
149+
if (forceToken.Count < 3)
150+
return new ErrorResponse("'force' array must contain at least 3 floats for 3D.");
151+
152+
var forceVec = new Vector3(
153+
forceToken[0].Value<float>(),
154+
forceToken[1].Value<float>(),
155+
forceToken[2].Value<float>());
156+
157+
if (positionToken != null)
158+
{
159+
if (positionToken.Count < 3)
160+
return new ErrorResponse("'position' array must contain at least 3 floats for 3D.");
161+
162+
var posVec = new Vector3(
163+
positionToken[0].Value<float>(),
164+
positionToken[1].Value<float>(),
165+
positionToken[2].Value<float>());
166+
rb.AddForceAtPosition(forceVec, posVec, mode);
167+
}
168+
else
169+
{
170+
rb.AddForce(forceVec, mode);
171+
}
172+
173+
responseData["force"] = new[] { forceVec.x, forceVec.y, forceVec.z };
174+
applied.Add("force");
175+
}
176+
177+
if (torqueToken != null)
178+
{
179+
var torqueArr = torqueToken as JArray;
180+
if (torqueArr == null || torqueArr.Count < 3)
181+
return new ErrorResponse("'torque' array must contain at least 3 floats for 3D.");
182+
183+
var torqueVec = new Vector3(
184+
torqueArr[0].Value<float>(),
185+
torqueArr[1].Value<float>(),
186+
torqueArr[2].Value<float>());
187+
rb.AddTorque(torqueVec, mode);
188+
responseData["torque"] = new[] { torqueVec.x, torqueVec.y, torqueVec.z };
189+
applied.Add("torque");
190+
}
191+
}
192+
193+
string appliedStr = string.Join(" and ", applied);
194+
return new
195+
{
196+
success = true,
197+
message = $"Applied {appliedStr} to '{go.name}'.",
198+
data = responseData
199+
};
200+
}
201+
202+
private static object ApplyExplosionForce(ToolParams p, GameObject go, bool is2D)
203+
{
204+
if (is2D)
205+
return new ErrorResponse("Explosion force is only available for 3D physics.");
206+
207+
float? explosionForce = p.GetFloat("explosion_force");
208+
if (explosionForce == null)
209+
return new ErrorResponse("'explosion_force' is required for explosion force type.");
210+
211+
var explosionPosToken = p.GetRaw("explosion_position") as JArray;
212+
if (explosionPosToken == null || explosionPosToken.Count < 3)
213+
return new ErrorResponse("'explosion_position' array (3 floats) is required for explosion force type.");
214+
215+
float? explosionRadius = p.GetFloat("explosion_radius");
216+
if (explosionRadius == null)
217+
return new ErrorResponse("'explosion_radius' is required for explosion force type.");
218+
219+
float upwardsModifier = p.GetFloat("upwards_modifier") ?? 0f;
220+
221+
string modeStr = p.Get("force_mode");
222+
ForceMode mode = ForceMode.Force;
223+
if (!string.IsNullOrEmpty(modeStr))
224+
{
225+
if (!Enum.TryParse<ForceMode>(modeStr, true, out mode))
226+
return new ErrorResponse($"Invalid ForceMode: '{modeStr}'. Valid values: Force, Impulse, Acceleration, VelocityChange.");
227+
}
228+
229+
var explosionPos = new Vector3(
230+
explosionPosToken[0].Value<float>(),
231+
explosionPosToken[1].Value<float>(),
232+
explosionPosToken[2].Value<float>());
233+
234+
var rb = go.GetComponent<Rigidbody>();
235+
rb.AddExplosionForce(explosionForce.Value, explosionPos, explosionRadius.Value, upwardsModifier, mode);
236+
237+
return new
238+
{
239+
success = true,
240+
message = $"Applied explosion force to '{go.name}'.",
241+
data = new
242+
{
243+
target = go.name,
244+
dimension = "3d",
245+
force_type = "explosion",
246+
force_mode = mode.ToString(),
247+
explosion_position = new[] { explosionPos.x, explosionPos.y, explosionPos.z },
248+
explosion_force = explosionForce.Value,
249+
explosion_radius = explosionRadius.Value,
250+
upwards_modifier = upwardsModifier
251+
}
252+
};
253+
}
254+
255+
private static GameObject FindTarget(JToken targetToken, string searchMethod)
256+
{
257+
if (targetToken == null)
258+
return null;
259+
260+
if (targetToken.Type == JTokenType.Integer)
261+
{
262+
int instanceId = targetToken.Value<int>();
263+
return GameObjectLookup.FindById(instanceId);
264+
}
265+
266+
string targetStr = targetToken.ToString();
267+
268+
if (int.TryParse(targetStr, out int parsedId))
269+
{
270+
var byId = GameObjectLookup.FindById(parsedId);
271+
if (byId != null)
272+
return byId;
273+
}
274+
275+
return GameObjectLookup.FindByTarget(targetToken, searchMethod ?? "by_name", true);
276+
}
277+
}
278+
}

0 commit comments

Comments
 (0)