Skip to content

Commit 87cd424

Browse files
committed
Add new Resource to request the full information from a game object and the component serialize data based on an instance ID request
Add new Tool to allow adding a component to a gameobject and changing it's data
1 parent 403d1c0 commit 87cd424

37 files changed

+1277
-226
lines changed
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Reflection;
4+
using UnityEngine;
5+
using Newtonsoft.Json.Linq;
6+
using UnityEditor;
7+
8+
namespace McpUnity.Resources
9+
{
10+
/// <summary>
11+
/// Resource for retrieving detailed information about a specific GameObject
12+
/// </summary>
13+
public class GetGameObjectResource : McpResourceBase
14+
{
15+
public GetGameObjectResource()
16+
{
17+
Name = "get_gameobject";
18+
Description = "Retrieves detailed information about a specific GameObject by instance ID";
19+
}
20+
21+
/// <summary>
22+
/// Fetch information about a specific GameObject
23+
/// </summary>
24+
/// <param name="parameters">Resource parameters as a JObject. Should include 'instanceId' for the GameObject to fetch</param>
25+
/// <returns>A JObject containing the GameObject data</returns>
26+
public override JObject Fetch(JObject parameters)
27+
{
28+
// Validate parameters
29+
if (parameters == null || !parameters.ContainsKey("instanceId"))
30+
{
31+
return new JObject
32+
{
33+
["success"] = false,
34+
["message"] = "Missing required parameter: instanceId"
35+
};
36+
}
37+
38+
// Try to parse the instance ID
39+
if (!int.TryParse(parameters["instanceId"].ToString(), out int instanceId))
40+
{
41+
return new JObject
42+
{
43+
["success"] = false,
44+
["message"] = "Invalid instanceId format. Must be an integer."
45+
};
46+
}
47+
48+
// Try to find the GameObject with the given instance ID
49+
GameObject gameObject = EditorUtility.InstanceIDToObject(instanceId) as GameObject;
50+
if (gameObject == null)
51+
{
52+
return new JObject
53+
{
54+
["success"] = false,
55+
["message"] = $"GameObject with instanceId {instanceId} not found"
56+
};
57+
}
58+
59+
// Convert the GameObject to a JObject
60+
JObject gameObjectData = GameObjectToJObject(gameObject, true);
61+
62+
// Create the response
63+
return new JObject
64+
{
65+
["success"] = true,
66+
["message"] = $"Retrieved GameObject data for '{gameObject.name}'",
67+
["gameObject"] = gameObjectData
68+
};
69+
}
70+
71+
/// <summary>
72+
/// Convert a GameObject to a JObject with its hierarchy
73+
/// </summary>
74+
/// <param name="gameObject">The GameObject to convert</param>
75+
/// <param name="includeDetailedComponents">Whether to include detailed component information</param>
76+
/// <returns>A JObject representing the GameObject</returns>
77+
public static JObject GameObjectToJObject(GameObject gameObject, bool includeDetailedComponents)
78+
{
79+
if (gameObject == null) return null;
80+
81+
// Add children
82+
JArray childrenArray = new JArray();
83+
foreach (Transform child in gameObject.transform)
84+
{
85+
childrenArray.Add(GameObjectToJObject(child.gameObject, includeDetailedComponents));
86+
}
87+
88+
// Create a JObject for the game object
89+
JObject gameObjectJson = new JObject
90+
{
91+
["name"] = gameObject.name,
92+
["activeSelf"] = gameObject.activeSelf,
93+
["activeInHierarchy"] = gameObject.activeInHierarchy,
94+
["tag"] = gameObject.tag,
95+
["layer"] = gameObject.layer,
96+
["layerName"] = LayerMask.LayerToName(gameObject.layer),
97+
["instanceId"] = gameObject.GetInstanceID(),
98+
["components"] = GetComponentsInfo(gameObject, includeDetailedComponents),
99+
["children"] = childrenArray
100+
};
101+
102+
return gameObjectJson;
103+
}
104+
105+
/// <summary>
106+
/// Get information about the components attached to a GameObject
107+
/// </summary>
108+
/// <param name="gameObject">The GameObject to get components from</param>
109+
/// <param name="includeDetailedInfo">Whether to include detailed component information</param>
110+
/// <returns>A JArray containing component information</returns>
111+
private static JArray GetComponentsInfo(GameObject gameObject, bool includeDetailedInfo = false)
112+
{
113+
Component[] components = gameObject.GetComponents<Component>();
114+
JArray componentsArray = new JArray();
115+
116+
foreach (Component component in components)
117+
{
118+
if (component == null) continue;
119+
120+
JObject componentJson = new JObject
121+
{
122+
["type"] = component.GetType().Name,
123+
["enabled"] = IsComponentEnabled(component)
124+
};
125+
126+
// Add detailed information if requested
127+
if (includeDetailedInfo)
128+
{
129+
componentJson["properties"] = GetComponentProperties(component);
130+
}
131+
132+
componentsArray.Add(componentJson);
133+
}
134+
135+
return componentsArray;
136+
}
137+
138+
/// <summary>
139+
/// Check if a component is enabled (if it has an enabled property)
140+
/// </summary>
141+
/// <param name="component">The component to check</param>
142+
/// <returns>True if the component is enabled, false otherwise</returns>
143+
private static bool IsComponentEnabled(Component component)
144+
{
145+
// Check if the component is a Behaviour (has enabled property)
146+
if (component is Behaviour behaviour)
147+
{
148+
return behaviour.enabled;
149+
}
150+
151+
// Check if the component is a Renderer
152+
if (component is Renderer renderer)
153+
{
154+
return renderer.enabled;
155+
}
156+
157+
// Check if the component is a Collider
158+
if (component is Collider collider)
159+
{
160+
return collider.enabled;
161+
}
162+
163+
// Default to true for components without an enabled property
164+
return true;
165+
}
166+
167+
/// <summary>
168+
/// Get all serialized fields, public fields and public properties of a component
169+
/// </summary>
170+
/// <param name="component">The component to get properties from</param>
171+
/// <returns>A JObject containing all the component properties</returns>
172+
private static JObject GetComponentProperties(Component component)
173+
{
174+
if (component == null) return null;
175+
176+
JObject propertiesJson = new JObject();
177+
Type componentType = component.GetType();
178+
179+
// Get serialized fields (both public and private with SerializeField attribute)
180+
FieldInfo[] fields = componentType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
181+
foreach (FieldInfo field in fields)
182+
{
183+
// Include public fields and serialized private fields
184+
bool isSerializedField = field.IsPublic || field.GetCustomAttributes(typeof(SerializeField), true).Length > 0;
185+
186+
if (!isSerializedField) continue;
187+
try
188+
{
189+
object value = field.GetValue(component);
190+
propertiesJson[field.Name] = SerializeValue(value);
191+
}
192+
catch (Exception)
193+
{
194+
// Skip fields that cannot be serialized
195+
propertiesJson[field.Name] = "Unable to serialize";
196+
}
197+
}
198+
199+
// Get public properties
200+
PropertyInfo[] properties = componentType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
201+
foreach (PropertyInfo property in properties)
202+
{
203+
// Only include properties with a getter and skip properties that might cause issues or are not useful
204+
if (!property.CanRead || ShouldSkipProperty(property)) continue;
205+
206+
try
207+
{
208+
object value = property.GetValue(component);
209+
propertiesJson[property.Name] = SerializeValue(value);
210+
}
211+
catch (Exception)
212+
{
213+
// Skip properties that cannot be serialized
214+
propertiesJson[property.Name] = "Unable to serialize";
215+
}
216+
}
217+
218+
return propertiesJson;
219+
}
220+
221+
/// <summary>
222+
/// Determine if a property should be skipped during serialization
223+
/// </summary>
224+
/// <param name="property">The property to check</param>
225+
/// <returns>True if the property should be skipped, false otherwise</returns>
226+
private static bool ShouldSkipProperty(PropertyInfo property)
227+
{
228+
// Skip properties that might cause issues or are not useful
229+
string[] skippedProperties = new string[]
230+
{
231+
"mesh", "sharedMesh", "material", "materials",
232+
"sharedMaterial", "sharedMaterials", "sprite",
233+
"mainTexture", "mainTextureOffset", "mainTextureScale"
234+
};
235+
236+
return Array.IndexOf(skippedProperties, property.Name.ToLower()) >= 0;
237+
}
238+
239+
/// <summary>
240+
/// Serialize a value to a JToken
241+
/// </summary>
242+
/// <param name="value">The value to serialize</param>
243+
/// <returns>A JToken representing the value</returns>
244+
private static JToken SerializeValue(object value)
245+
{
246+
if (value == null)
247+
return JValue.CreateNull();
248+
249+
// Handle common Unity types
250+
if (value is Vector2 vector2)
251+
return new JObject { ["x"] = vector2.x, ["y"] = vector2.y };
252+
253+
if (value is Vector3 vector3)
254+
return new JObject { ["x"] = vector3.x, ["y"] = vector3.y, ["z"] = vector3.z };
255+
256+
if (value is Vector4 vector4)
257+
return new JObject { ["x"] = vector4.x, ["y"] = vector4.y, ["z"] = vector4.z, ["w"] = vector4.w };
258+
259+
if (value is Quaternion quaternion)
260+
return new JObject { ["x"] = quaternion.x, ["y"] = quaternion.y, ["z"] = quaternion.z, ["w"] = quaternion.w };
261+
262+
if (value is Color color)
263+
return new JObject { ["r"] = color.r, ["g"] = color.g, ["b"] = color.b, ["a"] = color.a };
264+
265+
if (value is Bounds bounds)
266+
return new JObject {
267+
["center"] = SerializeValue(bounds.center),
268+
["size"] = SerializeValue(bounds.size)
269+
};
270+
271+
if (value is Rect rect)
272+
return new JObject { ["x"] = rect.x, ["y"] = rect.y, ["width"] = rect.width, ["height"] = rect.height };
273+
274+
if (value is UnityEngine.Object unityObject)
275+
return unityObject != null ? unityObject.name : null;
276+
277+
// Handle arrays and lists
278+
if (value is System.Collections.IList list)
279+
{
280+
JArray array = new JArray();
281+
foreach (var item in list)
282+
{
283+
array.Add(SerializeValue(item));
284+
}
285+
return array;
286+
}
287+
288+
// Handle dictionaries
289+
if (value is System.Collections.IDictionary dict)
290+
{
291+
JObject obj = new JObject();
292+
foreach (System.Collections.DictionaryEntry entry in dict)
293+
{
294+
obj[entry.Key.ToString()] = SerializeValue(entry.Value);
295+
}
296+
return obj;
297+
}
298+
299+
// Handle enum by using the name
300+
if (value is Enum enumValue)
301+
return enumValue.ToString();
302+
303+
// Use default serialization for primitive types
304+
return JToken.FromObject(value);
305+
}
306+
}
307+
}

Editor/Resources/GetGameObjectResource.cs.meta

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

0 commit comments

Comments
 (0)