ManageEditor.cs•16 kB
using System;
using MCPForUnity.Editor.Helpers;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEditorInternal; // Required for tag management
namespace MCPForUnity.Editor.Tools
{
/// <summary>
/// Handles editor control actions including play mode control, tool selection,
/// and tag/layer management. For reading editor state, use MCP resources instead.
/// </summary>
[McpForUnityTool("manage_editor", AutoRegister = false)]
public static class ManageEditor
{
// Constant for starting user layer index
private const int FirstUserLayerIndex = 8;
// Constant for total layer count
private const int TotalLayerCount = 32;
/// <summary>
/// Main handler for editor management actions.
/// </summary>
public static object HandleCommand(JObject @params)
{
string action = @params["action"]?.ToString().ToLower();
// Parameters for specific actions
string tagName = @params["tagName"]?.ToString();
string layerName = @params["layerName"]?.ToString();
bool waitForCompletion = @params["waitForCompletion"]?.ToObject<bool>() ?? false; // Example - not used everywhere
if (string.IsNullOrEmpty(action))
{
return new ErrorResponse("Action parameter is required.");
}
// Route action
switch (action)
{
// Play Mode Control
case "play":
try
{
if (!EditorApplication.isPlaying)
{
EditorApplication.isPlaying = true;
return new SuccessResponse("Entered play mode.");
}
return new SuccessResponse("Already in play mode.");
}
catch (Exception e)
{
return new ErrorResponse($"Error entering play mode: {e.Message}");
}
case "pause":
try
{
if (EditorApplication.isPlaying)
{
EditorApplication.isPaused = !EditorApplication.isPaused;
return new SuccessResponse(
EditorApplication.isPaused ? "Game paused." : "Game resumed."
);
}
return new ErrorResponse("Cannot pause/resume: Not in play mode.");
}
catch (Exception e)
{
return new ErrorResponse($"Error pausing/resuming game: {e.Message}");
}
case "stop":
try
{
if (EditorApplication.isPlaying)
{
EditorApplication.isPlaying = false;
return new SuccessResponse("Exited play mode.");
}
return new SuccessResponse("Already stopped (not in play mode).");
}
catch (Exception e)
{
return new ErrorResponse($"Error stopping play mode: {e.Message}");
}
// Tool Control
case "set_active_tool":
string toolName = @params["toolName"]?.ToString();
if (string.IsNullOrEmpty(toolName))
return new ErrorResponse("'toolName' parameter required for set_active_tool.");
return SetActiveTool(toolName);
// Tag Management
case "add_tag":
if (string.IsNullOrEmpty(tagName))
return new ErrorResponse("'tagName' parameter required for add_tag.");
return AddTag(tagName);
case "remove_tag":
if (string.IsNullOrEmpty(tagName))
return new ErrorResponse("'tagName' parameter required for remove_tag.");
return RemoveTag(tagName);
// Layer Management
case "add_layer":
if (string.IsNullOrEmpty(layerName))
return new ErrorResponse("'layerName' parameter required for add_layer.");
return AddLayer(layerName);
case "remove_layer":
if (string.IsNullOrEmpty(layerName))
return new ErrorResponse("'layerName' parameter required for remove_layer.");
return RemoveLayer(layerName);
// --- Settings (Example) ---
// case "set_resolution":
// int? width = @params["width"]?.ToObject<int?>();
// int? height = @params["height"]?.ToObject<int?>();
// if (!width.HasValue || !height.HasValue) return new ErrorResponse("'width' and 'height' parameters required.");
// return SetGameViewResolution(width.Value, height.Value);
// case "set_quality":
// // Handle string name or int index
// return SetQualityLevel(@params["qualityLevel"]);
default:
return new ErrorResponse(
$"Unknown action: '{action}'. Supported actions: play, pause, stop, set_active_tool, add_tag, remove_tag, add_layer, remove_layer. Use MCP resources for reading editor state, project info, tags, layers, selection, windows, prefab stage, and active tool."
);
}
}
// --- Tool Control Methods ---
private static object SetActiveTool(string toolName)
{
try
{
Tool targetTool;
if (Enum.TryParse<Tool>(toolName, true, out targetTool)) // Case-insensitive parse
{
// Check if it's a valid built-in tool
if (targetTool != Tool.None && targetTool <= Tool.Custom) // Tool.Custom is the last standard tool
{
UnityEditor.Tools.current = targetTool;
return new SuccessResponse($"Set active tool to '{targetTool}'.");
}
else
{
return new ErrorResponse(
$"Cannot directly set tool to '{toolName}'. It might be None, Custom, or invalid."
);
}
}
else
{
// Potentially try activating a custom tool by name here if needed
// This often requires specific editor scripting knowledge for that tool.
return new ErrorResponse(
$"Could not parse '{toolName}' as a standard Unity Tool (View, Move, Rotate, Scale, Rect, Transform, Custom)."
);
}
}
catch (Exception e)
{
return new ErrorResponse($"Error setting active tool: {e.Message}");
}
}
// --- Tag Management Methods ---
private static object AddTag(string tagName)
{
if (string.IsNullOrWhiteSpace(tagName))
return new ErrorResponse("Tag name cannot be empty or whitespace.");
// Check if tag already exists
if (System.Linq.Enumerable.Contains(InternalEditorUtility.tags, tagName))
{
return new ErrorResponse($"Tag '{tagName}' already exists.");
}
try
{
// Add the tag using the internal utility
InternalEditorUtility.AddTag(tagName);
// Force save assets to ensure the change persists in the TagManager asset
AssetDatabase.SaveAssets();
return new SuccessResponse($"Tag '{tagName}' added successfully.");
}
catch (Exception e)
{
return new ErrorResponse($"Failed to add tag '{tagName}': {e.Message}");
}
}
private static object RemoveTag(string tagName)
{
if (string.IsNullOrWhiteSpace(tagName))
return new ErrorResponse("Tag name cannot be empty or whitespace.");
if (tagName.Equals("Untagged", StringComparison.OrdinalIgnoreCase))
return new ErrorResponse("Cannot remove the built-in 'Untagged' tag.");
// Check if tag exists before attempting removal
if (!System.Linq.Enumerable.Contains(InternalEditorUtility.tags, tagName))
{
return new ErrorResponse($"Tag '{tagName}' does not exist.");
}
try
{
// Remove the tag using the internal utility
InternalEditorUtility.RemoveTag(tagName);
// Force save assets
AssetDatabase.SaveAssets();
return new SuccessResponse($"Tag '{tagName}' removed successfully.");
}
catch (Exception e)
{
// Catch potential issues if the tag is somehow in use or removal fails
return new ErrorResponse($"Failed to remove tag '{tagName}': {e.Message}");
}
}
// --- Layer Management Methods ---
private static object AddLayer(string layerName)
{
if (string.IsNullOrWhiteSpace(layerName))
return new ErrorResponse("Layer name cannot be empty or whitespace.");
// Access the TagManager asset
SerializedObject tagManager = GetTagManager();
if (tagManager == null)
return new ErrorResponse("Could not access TagManager asset.");
SerializedProperty layersProp = tagManager.FindProperty("layers");
if (layersProp == null || !layersProp.isArray)
return new ErrorResponse("Could not find 'layers' property in TagManager.");
// Check if layer name already exists (case-insensitive check recommended)
for (int i = 0; i < TotalLayerCount; i++)
{
SerializedProperty layerSP = layersProp.GetArrayElementAtIndex(i);
if (
layerSP != null
&& layerName.Equals(layerSP.stringValue, StringComparison.OrdinalIgnoreCase)
)
{
return new ErrorResponse($"Layer '{layerName}' already exists at index {i}.");
}
}
// Find the first empty user layer slot (indices 8 to 31)
int firstEmptyUserLayer = -1;
for (int i = FirstUserLayerIndex; i < TotalLayerCount; i++)
{
SerializedProperty layerSP = layersProp.GetArrayElementAtIndex(i);
if (layerSP != null && string.IsNullOrEmpty(layerSP.stringValue))
{
firstEmptyUserLayer = i;
break;
}
}
if (firstEmptyUserLayer == -1)
{
return new ErrorResponse("No empty User Layer slots available (8-31 are full).");
}
// Assign the name to the found slot
try
{
SerializedProperty targetLayerSP = layersProp.GetArrayElementAtIndex(
firstEmptyUserLayer
);
targetLayerSP.stringValue = layerName;
// Apply the changes to the TagManager asset
tagManager.ApplyModifiedProperties();
// Save assets to make sure it's written to disk
AssetDatabase.SaveAssets();
return new SuccessResponse(
$"Layer '{layerName}' added successfully to slot {firstEmptyUserLayer}."
);
}
catch (Exception e)
{
return new ErrorResponse($"Failed to add layer '{layerName}': {e.Message}");
}
}
private static object RemoveLayer(string layerName)
{
if (string.IsNullOrWhiteSpace(layerName))
return new ErrorResponse("Layer name cannot be empty or whitespace.");
// Access the TagManager asset
SerializedObject tagManager = GetTagManager();
if (tagManager == null)
return new ErrorResponse("Could not access TagManager asset.");
SerializedProperty layersProp = tagManager.FindProperty("layers");
if (layersProp == null || !layersProp.isArray)
return new ErrorResponse("Could not find 'layers' property in TagManager.");
// Find the layer by name (must be user layer)
int layerIndexToRemove = -1;
for (int i = FirstUserLayerIndex; i < TotalLayerCount; i++) // Start from user layers
{
SerializedProperty layerSP = layersProp.GetArrayElementAtIndex(i);
// Case-insensitive comparison is safer
if (
layerSP != null
&& layerName.Equals(layerSP.stringValue, StringComparison.OrdinalIgnoreCase)
)
{
layerIndexToRemove = i;
break;
}
}
if (layerIndexToRemove == -1)
{
return new ErrorResponse($"User layer '{layerName}' not found.");
}
// Clear the name for that index
try
{
SerializedProperty targetLayerSP = layersProp.GetArrayElementAtIndex(
layerIndexToRemove
);
targetLayerSP.stringValue = string.Empty; // Set to empty string to remove
// Apply the changes
tagManager.ApplyModifiedProperties();
// Save assets
AssetDatabase.SaveAssets();
return new SuccessResponse(
$"Layer '{layerName}' (slot {layerIndexToRemove}) removed successfully."
);
}
catch (Exception e)
{
return new ErrorResponse($"Failed to remove layer '{layerName}': {e.Message}");
}
}
// --- Helper Methods ---
/// <summary>
/// Gets the SerializedObject for the TagManager asset.
/// </summary>
private static SerializedObject GetTagManager()
{
try
{
// Load the TagManager asset from the ProjectSettings folder
UnityEngine.Object[] tagManagerAssets = AssetDatabase.LoadAllAssetsAtPath(
"ProjectSettings/TagManager.asset"
);
if (tagManagerAssets == null || tagManagerAssets.Length == 0)
{
McpLog.Error("[ManageEditor] TagManager.asset not found in ProjectSettings.");
return null;
}
// The first object in the asset file should be the TagManager
return new SerializedObject(tagManagerAssets[0]);
}
catch (Exception e)
{
McpLog.Error($"[ManageEditor] Error accessing TagManager.asset: {e.Message}");
return null;
}
}
// --- Example Implementations for Settings ---
/*
private static object SetGameViewResolution(int width, int height) { ... }
private static object SetQualityLevel(JToken qualityLevelToken) { ... }
*/
}
}