339 lines
15 KiB
C#
339 lines
15 KiB
C#
#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<bool>() ?? 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<GameObject>(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));
|
|
}
|
|
}
|
|
}
|