Unity MCP Server

by justinpbarnett
Verified
using UnityEngine; using Newtonsoft.Json.Linq; using System; using System.Linq; using System.Collections.Generic; using UnityEditor; using UnityEngine.SceneManagement; using UnityEditor.SceneManagement; using UnityMCP.Editor.Helpers; using System.Reflection; namespace UnityMCP.Editor.Commands { /// <summary> /// Handles object-related commands /// </summary> public static class ObjectCommandHandler { /// <summary> /// Gets information about a specific object /// </summary> public static object GetObjectInfo(JObject @params) { string name = (string)@params["name"] ?? throw new Exception("Parameter 'name' is required."); var obj = GameObject.Find(name) ?? throw new Exception($"Object '{name}' not found."); return new { obj.name, position = new[] { obj.transform.position.x, obj.transform.position.y, obj.transform.position.z }, rotation = new[] { obj.transform.eulerAngles.x, obj.transform.eulerAngles.y, obj.transform.eulerAngles.z }, scale = new[] { obj.transform.localScale.x, obj.transform.localScale.y, obj.transform.localScale.z } }; } /// <summary> /// Creates a new object in the scene /// </summary> public static object CreateObject(JObject @params) { string type = (string)@params["type"] ?? throw new Exception("Parameter 'type' is required."); GameObject obj = type.ToUpper() switch { "CUBE" => GameObject.CreatePrimitive(PrimitiveType.Cube), "SPHERE" => GameObject.CreatePrimitive(PrimitiveType.Sphere), "CYLINDER" => GameObject.CreatePrimitive(PrimitiveType.Cylinder), "CAPSULE" => GameObject.CreatePrimitive(PrimitiveType.Capsule), "PLANE" => GameObject.CreatePrimitive(PrimitiveType.Plane), "EMPTY" => new GameObject(), "CAMERA" => new GameObject("Camera") { }.AddComponent<Camera>().gameObject, "LIGHT" => new GameObject("Light") { }.AddComponent<Light>().gameObject, "DIRECTIONAL_LIGHT" => CreateDirectionalLight(), _ => throw new Exception($"Unsupported object type: {type}") }; if (@params.ContainsKey("name")) obj.name = (string)@params["name"]; if (@params.ContainsKey("location")) obj.transform.position = Vector3Helper.ParseVector3((JArray)@params["location"]); if (@params.ContainsKey("rotation")) obj.transform.eulerAngles = Vector3Helper.ParseVector3((JArray)@params["rotation"]); if (@params.ContainsKey("scale")) obj.transform.localScale = Vector3Helper.ParseVector3((JArray)@params["scale"]); return new { obj.name }; } /// <summary> /// Modifies an existing object's properties /// </summary> public static object ModifyObject(JObject @params) { string name = (string)@params["name"] ?? throw new Exception("Parameter 'name' is required."); var obj = GameObject.Find(name) ?? throw new Exception($"Object '{name}' not found."); // Handle basic transform properties if (@params.ContainsKey("location")) obj.transform.position = Vector3Helper.ParseVector3((JArray)@params["location"]); if (@params.ContainsKey("rotation")) obj.transform.eulerAngles = Vector3Helper.ParseVector3((JArray)@params["rotation"]); if (@params.ContainsKey("scale")) obj.transform.localScale = Vector3Helper.ParseVector3((JArray)@params["scale"]); if (@params.ContainsKey("visible")) obj.SetActive((bool)@params["visible"]); // Handle parent setting if (@params.ContainsKey("set_parent")) { string parentName = (string)@params["set_parent"]; var parent = GameObject.Find(parentName) ?? throw new Exception($"Parent object '{parentName}' not found."); obj.transform.SetParent(parent.transform); } // Handle component operations if (@params.ContainsKey("add_component")) { string componentType = (string)@params["add_component"]; Type type = componentType switch { "Rigidbody" => typeof(Rigidbody), "BoxCollider" => typeof(BoxCollider), "SphereCollider" => typeof(SphereCollider), "CapsuleCollider" => typeof(CapsuleCollider), "MeshCollider" => typeof(MeshCollider), "Camera" => typeof(Camera), "Light" => typeof(Light), "Renderer" => typeof(Renderer), "MeshRenderer" => typeof(MeshRenderer), "SkinnedMeshRenderer" => typeof(SkinnedMeshRenderer), "Animator" => typeof(Animator), "AudioSource" => typeof(AudioSource), "AudioListener" => typeof(AudioListener), "ParticleSystem" => typeof(ParticleSystem), "ParticleSystemRenderer" => typeof(ParticleSystemRenderer), "TrailRenderer" => typeof(TrailRenderer), "LineRenderer" => typeof(LineRenderer), "TextMesh" => typeof(TextMesh), "TextMeshPro" => typeof(TMPro.TextMeshPro), "TextMeshProUGUI" => typeof(TMPro.TextMeshProUGUI), _ => Type.GetType($"UnityEngine.{componentType}") ?? Type.GetType(componentType) ?? throw new Exception($"Component type '{componentType}' not found.") }; obj.AddComponent(type); } if (@params.ContainsKey("remove_component")) { string componentType = (string)@params["remove_component"]; Type type = Type.GetType($"UnityEngine.{componentType}") ?? Type.GetType(componentType) ?? throw new Exception($"Component type '{componentType}' not found."); var component = obj.GetComponent(type); if (component != null) UnityEngine.Object.DestroyImmediate(component); } // Handle property setting if (@params.ContainsKey("set_property")) { var propertyData = (JObject)@params["set_property"]; string componentType = (string)propertyData["component"]; string propertyName = (string)propertyData["property"]; var value = propertyData["value"]; // Handle GameObject properties separately if (componentType == "GameObject") { var gameObjectProperty = typeof(GameObject).GetProperty(propertyName) ?? throw new Exception($"Property '{propertyName}' not found on GameObject."); // Convert value based on property type object gameObjectValue = Convert.ChangeType(value, gameObjectProperty.PropertyType); gameObjectProperty.SetValue(obj, gameObjectValue); return new { obj.name }; } // Handle component properties Type type = componentType switch { "Rigidbody" => typeof(Rigidbody), "BoxCollider" => typeof(BoxCollider), "SphereCollider" => typeof(SphereCollider), "CapsuleCollider" => typeof(CapsuleCollider), "MeshCollider" => typeof(MeshCollider), "Camera" => typeof(Camera), "Light" => typeof(Light), "Renderer" => typeof(Renderer), "MeshRenderer" => typeof(MeshRenderer), "SkinnedMeshRenderer" => typeof(SkinnedMeshRenderer), "Animator" => typeof(Animator), "AudioSource" => typeof(AudioSource), "AudioListener" => typeof(AudioListener), "ParticleSystem" => typeof(ParticleSystem), "ParticleSystemRenderer" => typeof(ParticleSystemRenderer), "TrailRenderer" => typeof(TrailRenderer), "LineRenderer" => typeof(LineRenderer), "TextMesh" => typeof(TextMesh), "TextMeshPro" => typeof(TMPro.TextMeshPro), "TextMeshProUGUI" => typeof(TMPro.TextMeshProUGUI), _ => Type.GetType($"UnityEngine.{componentType}") ?? Type.GetType(componentType) ?? throw new Exception($"Component type '{componentType}' not found.") }; var component = obj.GetComponent(type) ?? throw new Exception($"Component '{componentType}' not found on object '{name}'."); var property = type.GetProperty(propertyName) ?? throw new Exception($"Property '{propertyName}' not found on component '{componentType}'."); // Convert value based on property type object propertyValue = Convert.ChangeType(value, property.PropertyType); property.SetValue(component, propertyValue); } return new { obj.name }; } /// <summary> /// Deletes an object from the scene /// </summary> public static object DeleteObject(JObject @params) { string name = (string)@params["name"] ?? throw new Exception("Parameter 'name' is required."); var obj = GameObject.Find(name) ?? throw new Exception($"Object '{name}' not found."); UnityEngine.Object.DestroyImmediate(obj); return new { name }; } /// <summary> /// Gets all properties of a specified game object /// </summary> public static object GetObjectProperties(JObject @params) { string name = (string)@params["name"] ?? throw new Exception("Parameter 'name' is required."); var obj = GameObject.Find(name) ?? throw new Exception($"Object '{name}' not found."); var components = obj.GetComponents<Component>() .Select(c => new { type = c.GetType().Name, properties = GetComponentProperties(c) }) .ToList(); return new { obj.name, obj.tag, obj.layer, active = obj.activeSelf, transform = new { position = new[] { obj.transform.position.x, obj.transform.position.y, obj.transform.position.z }, rotation = new[] { obj.transform.eulerAngles.x, obj.transform.eulerAngles.y, obj.transform.eulerAngles.z }, scale = new[] { obj.transform.localScale.x, obj.transform.localScale.y, obj.transform.localScale.z } }, components }; } /// <summary> /// Gets properties of a specific component /// </summary> public static object GetComponentProperties(JObject @params) { string objectName = (string)@params["object_name"] ?? throw new Exception("Parameter 'object_name' is required."); string componentType = (string)@params["component_type"] ?? throw new Exception("Parameter 'component_type' is required."); var obj = GameObject.Find(objectName) ?? throw new Exception($"Object '{objectName}' not found."); var component = obj.GetComponent(componentType) ?? throw new Exception($"Component '{componentType}' not found on object '{objectName}'."); return GetComponentProperties(component); } /// <summary> /// Finds objects by name in the scene /// </summary> public static object FindObjectsByName(JObject @params) { string name = (string)@params["name"] ?? throw new Exception("Parameter 'name' is required."); var objects = GameObject.FindObjectsByType<GameObject>(FindObjectsSortMode.None) .Where(o => o.name.Contains(name)) .Select(o => new { o.name, path = GetGameObjectPath(o) }) .ToList(); return new { objects }; } /// <summary> /// Finds objects by tag in the scene /// </summary> public static object FindObjectsByTag(JObject @params) { string tag = (string)@params["tag"] ?? throw new Exception("Parameter 'tag' is required."); var objects = GameObject.FindGameObjectsWithTag(tag) .Select(o => new { o.name, path = GetGameObjectPath(o) }) .ToList(); return new { objects }; } /// <summary> /// Gets the current hierarchy of game objects in the scene /// </summary> public static object GetHierarchy() { var rootObjects = SceneManager.GetActiveScene().GetRootGameObjects(); var hierarchy = rootObjects.Select(o => BuildHierarchyNode(o)).ToList(); return new { hierarchy }; } /// <summary> /// Selects a specified game object in the editor /// </summary> public static object SelectObject(JObject @params) { string name = (string)@params["name"] ?? throw new Exception("Parameter 'name' is required."); var obj = GameObject.Find(name) ?? throw new Exception($"Object '{name}' not found."); Selection.activeGameObject = obj; return new { obj.name }; } /// <summary> /// Gets the currently selected game object in the editor /// </summary> public static object GetSelectedObject() { var selected = Selection.activeGameObject; if (selected == null) return new { selected = (object)null }; return new { selected = new { selected.name, path = GetGameObjectPath(selected) } }; } // Helper methods private static Dictionary<string, object> GetComponentProperties(Component component) { var properties = new Dictionary<string, object>(); var serializedObject = new SerializedObject(component); var property = serializedObject.GetIterator(); while (property.Next(true)) { properties[property.name] = GetPropertyValue(property); } return properties; } private static object GetPropertyValue(SerializedProperty property) { switch (property.propertyType) { case SerializedPropertyType.Integer: return property.intValue; case SerializedPropertyType.Float: return property.floatValue; case SerializedPropertyType.Boolean: return property.boolValue; case SerializedPropertyType.String: return property.stringValue; case SerializedPropertyType.Vector3: return new[] { property.vector3Value.x, property.vector3Value.y, property.vector3Value.z }; case SerializedPropertyType.Vector2: return new[] { property.vector2Value.x, property.vector2Value.y }; case SerializedPropertyType.Color: return new[] { property.colorValue.r, property.colorValue.g, property.colorValue.b, property.colorValue.a }; case SerializedPropertyType.ObjectReference: return property.objectReferenceValue ? property.objectReferenceValue.name : null; default: return property.propertyType.ToString(); } } private static string GetGameObjectPath(GameObject obj) { var path = obj.name; var parent = obj.transform.parent; while (parent != null) { path = parent.name + "/" + path; parent = parent.parent; } return path; } private static object BuildHierarchyNode(GameObject obj) { return new { obj.name, children = Enumerable.Range(0, obj.transform.childCount) .Select(i => BuildHierarchyNode(obj.transform.GetChild(i).gameObject)) .ToList() }; } /// <summary> /// Creates a directional light game object /// </summary> private static GameObject CreateDirectionalLight() { var obj = new GameObject("DirectionalLight"); var light = obj.AddComponent<Light>(); light.type = LightType.Directional; light.intensity = 1.0f; light.shadows = LightShadows.Soft; return obj; } /// <summary> /// Executes a context menu method on a component of a game object /// </summary> public static object ExecuteContextMenuItem(JObject @params) { string objectName = (string)@params["object_name"] ?? throw new Exception("Parameter 'object_name' is required."); string componentName = (string)@params["component"] ?? throw new Exception("Parameter 'component' is required."); string contextMenuItemName = (string)@params["context_menu_item"] ?? throw new Exception("Parameter 'context_menu_item' is required."); // Find the game object var obj = GameObject.Find(objectName) ?? throw new Exception($"Object '{objectName}' not found."); // Find the component type Type componentType = FindTypeInLoadedAssemblies(componentName) ?? throw new Exception($"Component type '{componentName}' not found."); // Get the component from the game object var component = obj.GetComponent(componentType) ?? throw new Exception($"Component '{componentName}' not found on object '{objectName}'."); // Find methods with ContextMenu attribute matching the context menu item name var methods = componentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Where(m => m.GetCustomAttributes(typeof(ContextMenuItemAttribute), true).Any() || m.GetCustomAttributes(typeof(ContextMenu), true) .Cast<ContextMenu>() .Any(attr => attr.menuItem == contextMenuItemName)) .ToList(); // If no methods with ContextMenuItemAttribute are found, look for methods with name matching the context menu item if (methods.Count == 0) { methods = componentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Where(m => m.Name == contextMenuItemName) .ToList(); } if (methods.Count == 0) throw new Exception($"No context menu method '{contextMenuItemName}' found on component '{componentName}'."); // If multiple methods match, use the first one and log a warning if (methods.Count > 1) { Debug.LogWarning($"Found multiple methods for context menu item '{contextMenuItemName}' on component '{componentName}'. Using the first one."); } var method = methods[0]; // Execute the method try { method.Invoke(component, null); return new { success = true, message = $"Successfully executed context menu item '{contextMenuItemName}' on component '{componentName}' of object '{objectName}'." }; } catch (Exception ex) { throw new Exception($"Error executing context menu item: {ex.Message}"); } } // Add this helper method to find types across all loaded assemblies private static Type FindTypeInLoadedAssemblies(string typeName) { // First try standard approach Type type = Type.GetType(typeName); if (type != null) return type; type = Type.GetType($"UnityEngine.{typeName}"); if (type != null) return type; // Then search all loaded assemblies foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { // Try with the simple name type = assembly.GetType(typeName); if (type != null) return type; // Try with the fully qualified name (assembly.GetTypes() can be expensive, so we do this last) var types = assembly.GetTypes().Where(t => t.Name == typeName).ToArray(); if (types.Length > 0) { // If we found multiple types with the same name, log a warning if (types.Length > 1) { Debug.LogWarning( $"Found multiple types named '{typeName}'. Using the first one: {types[0].FullName}" ); } return types[0]; } } return null; } } }