#nullable disable using System; using System.Linq; using MCPForUnity.Editor.Helpers; using Newtonsoft.Json.Linq; using UnityEditor; using UnityEditor.SceneManagement; using UnityEditorInternal; using UnityEngine; namespace MCPForUnity.Editor.Tools.GameObjects { internal static class GameObjectCreate { internal static object Handle(JObject @params) { string name = @params["name"]?.ToString(); if (string.IsNullOrEmpty(name)) { return new ErrorResponse("'name' parameter is required for 'create' action."); } // Get prefab creation parameters bool saveAsPrefab = @params["saveAsPrefab"]?.ToObject() ?? false; string prefabPath = @params["prefabPath"]?.ToString(); string tag = @params["tag"]?.ToString(); string primitiveType = @params["primitiveType"]?.ToString(); GameObject newGo = null; // --- Try Instantiating Prefab First --- string originalPrefabPath = prefabPath; if (!saveAsPrefab && !string.IsNullOrEmpty(prefabPath)) { string extension = System.IO.Path.GetExtension(prefabPath); if (!prefabPath.Contains("/") && (string.IsNullOrEmpty(extension) || extension.Equals(".prefab", StringComparison.OrdinalIgnoreCase))) { string prefabNameOnly = prefabPath; McpLog.Info($"[ManageGameObject.Create] Searching for prefab named: '{prefabNameOnly}'"); string[] guids = AssetDatabase.FindAssets($"t:Prefab {prefabNameOnly}"); if (guids.Length == 0) { return new ErrorResponse($"Prefab named '{prefabNameOnly}' not found anywhere in the project."); } else if (guids.Length > 1) { string foundPaths = string.Join(", ", guids.Select(g => AssetDatabase.GUIDToAssetPath(g))); return new ErrorResponse($"Multiple prefabs found matching name '{prefabNameOnly}': {foundPaths}. Please provide a more specific path."); } else { prefabPath = AssetDatabase.GUIDToAssetPath(guids[0]); McpLog.Info($"[ManageGameObject.Create] Found unique prefab at path: '{prefabPath}'"); } } else if (prefabPath.Contains("/") && string.IsNullOrEmpty(extension)) { McpLog.Warn($"[ManageGameObject.Create] Provided prefabPath '{prefabPath}' has no extension. Assuming it's a prefab and appending .prefab."); prefabPath += ".prefab"; } else if (!prefabPath.Contains("/") && !string.IsNullOrEmpty(extension) && !extension.Equals(".prefab", StringComparison.OrdinalIgnoreCase)) { string fileName = prefabPath; string fileNameWithoutExtension = System.IO.Path.GetFileNameWithoutExtension(fileName); McpLog.Info($"[ManageGameObject.Create] Searching for asset file named: '{fileName}'"); string[] guids = AssetDatabase.FindAssets(fileNameWithoutExtension); var matches = guids .Select(g => AssetDatabase.GUIDToAssetPath(g)) .Where(p => p.EndsWith("/" + fileName, StringComparison.OrdinalIgnoreCase) || p.Equals(fileName, StringComparison.OrdinalIgnoreCase)) .ToArray(); if (matches.Length == 0) { return new ErrorResponse($"Asset file '{fileName}' not found anywhere in the project."); } else if (matches.Length > 1) { string foundPaths = string.Join(", ", matches); return new ErrorResponse($"Multiple assets found matching file name '{fileName}': {foundPaths}. Please provide a more specific path."); } else { prefabPath = matches[0]; McpLog.Info($"[ManageGameObject.Create] Found unique asset at path: '{prefabPath}'"); } } GameObject prefabAsset = AssetDatabase.LoadAssetAtPath(prefabPath); if (prefabAsset != null) { try { newGo = PrefabUtility.InstantiatePrefab(prefabAsset) as GameObject; if (newGo == null) { McpLog.Error($"[ManageGameObject.Create] Failed to instantiate prefab at '{prefabPath}', asset might be corrupted or not a GameObject."); return new ErrorResponse($"Failed to instantiate prefab at '{prefabPath}'."); } if (!string.IsNullOrEmpty(name)) { newGo.name = name; } Undo.RegisterCreatedObjectUndo(newGo, $"Instantiate Prefab '{prefabAsset.name}' as '{newGo.name}'"); McpLog.Info($"[ManageGameObject.Create] Instantiated prefab '{prefabAsset.name}' from path '{prefabPath}' as '{newGo.name}'."); } catch (Exception e) { return new ErrorResponse($"Error instantiating prefab '{prefabPath}': {e.Message}"); } } else { return new ErrorResponse($"Asset not found or not a GameObject at path: '{prefabPath}'."); } } // --- Fallback: Create Primitive or Empty GameObject --- bool createdNewObject = false; if (newGo == null) { if (!string.IsNullOrEmpty(primitiveType)) { try { PrimitiveType type = (PrimitiveType)Enum.Parse(typeof(PrimitiveType), primitiveType, true); newGo = GameObject.CreatePrimitive(type); if (!string.IsNullOrEmpty(name)) { newGo.name = name; } else { UnityEngine.Object.DestroyImmediate(newGo); return new ErrorResponse("'name' parameter is required when creating a primitive."); } createdNewObject = true; } catch (ArgumentException) { return new ErrorResponse($"Invalid primitive type: '{primitiveType}'. Valid types: {string.Join(", ", Enum.GetNames(typeof(PrimitiveType)))}"); } catch (Exception e) { return new ErrorResponse($"Failed to create primitive '{primitiveType}': {e.Message}"); } } else { if (string.IsNullOrEmpty(name)) { return new ErrorResponse("'name' parameter is required for 'create' action when not instantiating a prefab or creating a primitive."); } newGo = new GameObject(name); createdNewObject = true; } if (createdNewObject) { Undo.RegisterCreatedObjectUndo(newGo, $"Create GameObject '{newGo.name}'"); } } if (newGo == null) { return new ErrorResponse("Failed to create or instantiate the GameObject."); } Undo.RecordObject(newGo.transform, "Set GameObject Transform"); Undo.RecordObject(newGo, "Set GameObject Properties"); // Set Parent JToken parentToken = @params["parent"]; if (parentToken != null) { GameObject parentGo = ManageGameObjectCommon.FindObjectInternal(parentToken, "by_id_or_name_or_path"); if (parentGo == null) { UnityEngine.Object.DestroyImmediate(newGo); return new ErrorResponse($"Parent specified ('{parentToken}') but not found."); } newGo.transform.SetParent(parentGo.transform, true); } // Set Transform Vector3? position = VectorParsing.ParseVector3(@params["position"]); Vector3? rotation = VectorParsing.ParseVector3(@params["rotation"]); Vector3? scale = VectorParsing.ParseVector3(@params["scale"]); if (position.HasValue) newGo.transform.localPosition = position.Value; if (rotation.HasValue) newGo.transform.localEulerAngles = rotation.Value; if (scale.HasValue) newGo.transform.localScale = scale.Value; // Set Tag if (!string.IsNullOrEmpty(tag)) { if (tag != "Untagged" && !System.Linq.Enumerable.Contains(InternalEditorUtility.tags, tag)) { McpLog.Info($"[ManageGameObject.Create] Tag '{tag}' not found. Creating it."); try { InternalEditorUtility.AddTag(tag); } catch (Exception ex) { UnityEngine.Object.DestroyImmediate(newGo); return new ErrorResponse($"Failed to create tag '{tag}': {ex.Message}."); } } try { newGo.tag = tag; } catch (Exception ex) { UnityEngine.Object.DestroyImmediate(newGo); return new ErrorResponse($"Failed to set tag to '{tag}' during creation: {ex.Message}."); } } // Set Layer string layerName = @params["layer"]?.ToString(); if (!string.IsNullOrEmpty(layerName)) { int layerId = LayerMask.NameToLayer(layerName); if (layerId != -1) { newGo.layer = layerId; } else { McpLog.Warn($"[ManageGameObject.Create] Layer '{layerName}' not found. Using default layer."); } } // Add Components if (@params["componentsToAdd"] is JArray componentsToAddArray) { foreach (var compToken in componentsToAddArray) { string typeName = null; JObject properties = null; if (compToken.Type == JTokenType.String) { typeName = compToken.ToString(); } else if (compToken is JObject compObj) { typeName = compObj["typeName"]?.ToString(); properties = compObj["properties"] as JObject; } if (!string.IsNullOrEmpty(typeName)) { var addResult = GameObjectComponentHelpers.AddComponentInternal(newGo, typeName, properties); if (addResult != null) { UnityEngine.Object.DestroyImmediate(newGo); return addResult; } } else { McpLog.Warn($"[ManageGameObject] Invalid component format in componentsToAdd: {compToken}"); } } } // Save as Prefab ONLY if we *created* a new object AND saveAsPrefab is true GameObject finalInstance = newGo; if (createdNewObject && saveAsPrefab) { string finalPrefabPath = prefabPath; if (string.IsNullOrEmpty(finalPrefabPath)) { UnityEngine.Object.DestroyImmediate(newGo); return new ErrorResponse("'prefabPath' is required when 'saveAsPrefab' is true and creating a new object."); } if (!finalPrefabPath.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase)) { McpLog.Info($"[ManageGameObject.Create] Appending .prefab extension to save path: '{finalPrefabPath}' -> '{finalPrefabPath}.prefab'"); finalPrefabPath += ".prefab"; } try { string directoryPath = System.IO.Path.GetDirectoryName(finalPrefabPath); if (!string.IsNullOrEmpty(directoryPath) && !System.IO.Directory.Exists(directoryPath)) { System.IO.Directory.CreateDirectory(directoryPath); AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); McpLog.Info($"[ManageGameObject.Create] Created directory for prefab: {directoryPath}"); } finalInstance = PrefabUtility.SaveAsPrefabAssetAndConnect(newGo, finalPrefabPath, InteractionMode.UserAction); if (finalInstance == null) { UnityEngine.Object.DestroyImmediate(newGo); return new ErrorResponse($"Failed to save GameObject '{name}' as prefab at '{finalPrefabPath}'. Check path and permissions."); } McpLog.Info($"[ManageGameObject.Create] GameObject '{name}' saved as prefab to '{finalPrefabPath}' and instance connected."); } catch (Exception e) { UnityEngine.Object.DestroyImmediate(newGo); return new ErrorResponse($"Error saving prefab '{finalPrefabPath}': {e.Message}"); } } Selection.activeGameObject = finalInstance; string messagePrefabPath = finalInstance == null ? originalPrefabPath : AssetDatabase.GetAssetPath(PrefabUtility.GetCorrespondingObjectFromSource(finalInstance) ?? (UnityEngine.Object)finalInstance); string successMessage; if (!createdNewObject && !string.IsNullOrEmpty(messagePrefabPath)) { successMessage = $"Prefab '{messagePrefabPath}' instantiated successfully as '{finalInstance.name}'."; } else if (createdNewObject && saveAsPrefab && !string.IsNullOrEmpty(messagePrefabPath)) { successMessage = $"GameObject '{finalInstance.name}' created and saved as prefab to '{messagePrefabPath}'."; } else { successMessage = $"GameObject '{finalInstance.name}' created successfully in scene."; } return new SuccessResponse(successMessage, Helpers.GameObjectSerializer.GetGameObjectData(finalInstance)); } } }