using System; using System.Reflection; using MCPForUnity.Editor.Helpers; using Newtonsoft.Json.Linq; using UnityEditor; using UnityEngine; using MCPForUnity.Runtime.Helpers; namespace MCPForUnity.Editor.Tools.Cameras { internal static class CameraConfigure { #region Tier 1 — Basic Camera internal static object SetBasicCameraTarget(JObject @params) { var go = CameraHelpers.FindTargetGameObject(@params); if (go == null) return new ErrorResponse("Target Camera not found."); var cam = go.GetComponent(); if (cam == null) return new ErrorResponse($"No Camera component on '{go.name}'."); var props = CameraHelpers.ExtractProperties(@params) ?? new JObject(); var lookAtToken = props["lookAt"] ?? props["look_at"] ?? props["follow"]; if (lookAtToken == null) return new ErrorResponse("'follow' or 'lookAt' property is required."); var target = CameraHelpers.ResolveGameObjectRef(lookAtToken); if (target == null) return new ErrorResponse($"Target '{lookAtToken}' not found."); Undo.RecordObject(go.transform, "Set Camera Target"); go.transform.LookAt(target.transform); CameraHelpers.MarkDirty(go); return new { success = true, message = $"Camera '{go.name}' now looking at '{target.name}'.", data = new { instanceID = go.GetInstanceIDCompat() } }; } internal static object SetBasicCameraLens(JObject @params) { var go = CameraHelpers.FindTargetGameObject(@params); if (go == null) return new ErrorResponse("Target Camera not found."); var cam = go.GetComponent(); if (cam == null) return new ErrorResponse($"No Camera component on '{go.name}'."); var props = CameraHelpers.ExtractProperties(@params) ?? new JObject(); Undo.RecordObject(cam, "Set Camera Lens"); if (props["fieldOfView"] != null) cam.fieldOfView = ParamCoercion.CoerceFloat(props["fieldOfView"], cam.fieldOfView); if (props["nearClipPlane"] != null) cam.nearClipPlane = ParamCoercion.CoerceFloat(props["nearClipPlane"], cam.nearClipPlane); if (props["farClipPlane"] != null) cam.farClipPlane = ParamCoercion.CoerceFloat(props["farClipPlane"], cam.farClipPlane); if (props["orthographicSize"] != null) cam.orthographicSize = ParamCoercion.CoerceFloat(props["orthographicSize"], cam.orthographicSize); CameraHelpers.MarkDirty(go); return new { success = true, message = $"Lens properties set on Camera '{go.name}'.", data = new { instanceID = go.GetInstanceIDCompat() } }; } internal static object SetBasicCameraPriority(JObject @params) { var go = CameraHelpers.FindTargetGameObject(@params); if (go == null) return new ErrorResponse("Target Camera not found."); var cam = go.GetComponent(); if (cam == null) return new ErrorResponse($"No Camera component on '{go.name}'."); var props = CameraHelpers.ExtractProperties(@params) ?? new JObject(); float depth = ParamCoercion.CoerceFloat(props["priority"], cam.depth); Undo.RecordObject(cam, "Set Camera Depth"); cam.depth = depth; CameraHelpers.MarkDirty(go); return new { success = true, message = $"Camera '{go.name}' depth set to {depth}.", data = new { instanceID = go.GetInstanceIDCompat(), depth } }; } #endregion #region Tier 2 — Cinemachine internal static object SetCinemachineTarget(JObject @params) { var cmCamera = CameraHelpers.FindCinemachineCamera(@params); if (cmCamera == null) return new ErrorResponse("Target CinemachineCamera not found."); var props = CameraHelpers.ExtractProperties(@params) ?? new JObject(); Undo.RecordObject(cmCamera, "Set Cinemachine Target"); if (props.ContainsKey("follow")) CameraHelpers.SetTransformTarget(cmCamera, "Follow", props["follow"]); if (props.ContainsKey("lookAt") || props.ContainsKey("look_at")) CameraHelpers.SetTransformTarget(cmCamera, "LookAt", props["lookAt"] ?? props["look_at"]); CameraHelpers.MarkDirty(cmCamera.gameObject); return new { success = true, message = $"Targets set on CinemachineCamera '{cmCamera.gameObject.name}'.", data = new { instanceID = cmCamera.gameObject.GetInstanceIDCompat() } }; } internal static object SetCinemachineLens(JObject @params) { var cmCamera = CameraHelpers.FindCinemachineCamera(@params); if (cmCamera == null) return new ErrorResponse("Target CinemachineCamera not found."); var props = CameraHelpers.ExtractProperties(@params) ?? new JObject(); Undo.RecordObject(cmCamera, "Set Cinemachine Lens"); // Lens is a struct field — use SerializedProperty for reliable setting using var so = new SerializedObject(cmCamera); var lensProp = so.FindProperty("Lens") ?? so.FindProperty("m_Lens"); if (lensProp == null) return new ErrorResponse("Could not find Lens property on CinemachineCamera."); SetFloatSubProp(lensProp, "FieldOfView", props["fieldOfView"]); SetFloatSubProp(lensProp, "NearClipPlane", props["nearClipPlane"]); SetFloatSubProp(lensProp, "FarClipPlane", props["farClipPlane"]); SetFloatSubProp(lensProp, "OrthographicSize", props["orthographicSize"]); SetFloatSubProp(lensProp, "Dutch", props["dutch"]); so.ApplyModifiedProperties(); CameraHelpers.MarkDirty(cmCamera.gameObject); return new { success = true, message = $"Lens properties set on CinemachineCamera '{cmCamera.gameObject.name}'.", data = new { instanceID = cmCamera.gameObject.GetInstanceIDCompat() } }; } internal static object SetCinemachinePriority(JObject @params) { var cmCamera = CameraHelpers.FindCinemachineCamera(@params); if (cmCamera == null) return new ErrorResponse("Target CinemachineCamera not found."); var props = CameraHelpers.ExtractProperties(@params) ?? new JObject(); int priority = ParamCoercion.CoerceInt(props["priority"], 10); // PrioritySettings is a struct with Enabled + m_Value — use SerializedProperty using var so = new SerializedObject(cmCamera); var priorityProp = so.FindProperty("Priority"); if (priorityProp != null) { var enabledProp = priorityProp.FindPropertyRelative("Enabled"); var valueProp = priorityProp.FindPropertyRelative("m_Value"); if (enabledProp != null) enabledProp.boolValue = true; if (valueProp != null) valueProp.intValue = priority; so.ApplyModifiedProperties(); } else { Undo.RecordObject(cmCamera, "Set Cinemachine Priority"); CameraHelpers.SetReflectionProperty(cmCamera, "Priority", priority); } CameraHelpers.MarkDirty(cmCamera.gameObject); return new { success = true, message = $"Priority set to {priority} on CinemachineCamera '{cmCamera.gameObject.name}'.", data = new { instanceID = cmCamera.gameObject.GetInstanceIDCompat(), priority } }; } internal static object SetBody(JObject @params) { var cmCamera = CameraHelpers.FindCinemachineCamera(@params); if (cmCamera == null) return new ErrorResponse("Target CinemachineCamera not found."); var props = CameraHelpers.ExtractProperties(@params) ?? new JObject(); var go = cmCamera.gameObject; // Optionally swap body component string bodyTypeName = ParamCoercion.CoerceString(props["bodyType"] ?? props["body_type"], null); Component bodyComponent; if (bodyTypeName != null) { bodyComponent = SwapPipelineComponent(go, "Body", bodyTypeName); if (bodyComponent == null) return new ErrorResponse($"Could not resolve body component type '{bodyTypeName}'."); } else { bodyComponent = CameraHelpers.GetPipelineComponent(cmCamera, "Body"); if (bodyComponent == null) return new ErrorResponse("No Body component found on this CinemachineCamera. Provide 'bodyType' to add one."); } // Set properties on body component SetComponentProperties(bodyComponent, props, new[] { "bodyType", "body_type" }); CameraHelpers.MarkDirty(go); return new { success = true, message = $"Body configured on CinemachineCamera '{go.name}'.", data = new { instanceID = go.GetInstanceIDCompat(), body = bodyComponent.GetType().Name } }; } internal static object SetAim(JObject @params) { var cmCamera = CameraHelpers.FindCinemachineCamera(@params); if (cmCamera == null) return new ErrorResponse("Target CinemachineCamera not found."); var props = CameraHelpers.ExtractProperties(@params) ?? new JObject(); var go = cmCamera.gameObject; string aimTypeName = ParamCoercion.CoerceString(props["aimType"] ?? props["aim_type"], null); Component aimComponent; if (aimTypeName != null) { aimComponent = SwapPipelineComponent(go, "Aim", aimTypeName); if (aimComponent == null) return new ErrorResponse($"Could not resolve aim component type '{aimTypeName}'."); } else { aimComponent = CameraHelpers.GetPipelineComponent(cmCamera, "Aim"); if (aimComponent == null) return new ErrorResponse("No Aim component found. Provide 'aimType' to add one."); } SetComponentProperties(aimComponent, props, new[] { "aimType", "aim_type" }); CameraHelpers.MarkDirty(go); return new { success = true, message = $"Aim configured on CinemachineCamera '{go.name}'.", data = new { instanceID = go.GetInstanceIDCompat(), aim = aimComponent.GetType().Name } }; } internal static object SetNoise(JObject @params) { var cmCamera = CameraHelpers.FindCinemachineCamera(@params); if (cmCamera == null) return new ErrorResponse("Target CinemachineCamera not found."); var props = CameraHelpers.ExtractProperties(@params) ?? new JObject(); var go = cmCamera.gameObject; // Get or add noise component var noiseType = CameraHelpers.ResolveComponentType("CinemachineBasicMultiChannelPerlin"); if (noiseType == null) return new ErrorResponse("CinemachineBasicMultiChannelPerlin type not found."); var noiseComponent = go.GetComponent(noiseType); bool added = false; if (noiseComponent == null) { noiseComponent = Undo.AddComponent(go, noiseType); added = true; } Undo.RecordObject(noiseComponent, "Set Cinemachine Noise"); SetComponentProperties(noiseComponent, props, Array.Empty()); CameraHelpers.MarkDirty(go); return new { success = true, message = added ? $"Added noise to CinemachineCamera '{go.name}'." : $"Noise configured on CinemachineCamera '{go.name}'.", data = new { instanceID = go.GetInstanceIDCompat(), added } }; } internal static object AddExtension(JObject @params) { var cmCamera = CameraHelpers.FindCinemachineCamera(@params); if (cmCamera == null) return new ErrorResponse("Target CinemachineCamera not found."); var props = CameraHelpers.ExtractProperties(@params) ?? new JObject(); string extTypeName = ParamCoercion.CoerceString( props["extensionType"] ?? props["extension_type"], null); if (string.IsNullOrEmpty(extTypeName)) return new ErrorResponse("'extensionType' property is required."); var extType = CameraHelpers.ResolveComponentType(extTypeName); if (extType == null) return new ErrorResponse($"Extension type '{extTypeName}' not found."); var go = cmCamera.gameObject; var existing = go.GetComponent(extType); if (existing != null) return new { success = true, message = $"Extension '{extTypeName}' already exists on '{go.name}'." }; var ext = Undo.AddComponent(go, extType); SetComponentProperties(ext, props, new[] { "extensionType", "extension_type" }); CameraHelpers.MarkDirty(go); return new { success = true, message = $"Extension '{extTypeName}' added to CinemachineCamera '{go.name}'.", data = new { instanceID = go.GetInstanceIDCompat(), extensionType = extTypeName } }; } internal static object RemoveExtension(JObject @params) { var cmCamera = CameraHelpers.FindCinemachineCamera(@params); if (cmCamera == null) return new ErrorResponse("Target CinemachineCamera not found."); var props = CameraHelpers.ExtractProperties(@params) ?? new JObject(); string extTypeName = ParamCoercion.CoerceString( props["extensionType"] ?? props["extension_type"], null); if (string.IsNullOrEmpty(extTypeName)) return new ErrorResponse("'extensionType' property is required."); var extType = CameraHelpers.ResolveComponentType(extTypeName); if (extType == null) return new ErrorResponse($"Extension type '{extTypeName}' not found."); var go = cmCamera.gameObject; var ext = go.GetComponent(extType); if (ext == null) return new ErrorResponse($"Extension '{extTypeName}' not found on '{go.name}'."); Undo.DestroyObjectImmediate(ext); CameraHelpers.MarkDirty(go); return new { success = true, message = $"Extension '{extTypeName}' removed from CinemachineCamera '{go.name}'.", data = new { instanceID = go.GetInstanceIDCompat() } }; } #endregion #region Helpers private static void SetFloatSubProp(SerializedProperty parent, string subPropName, JToken value) { if (value == null || value.Type == JTokenType.Null) return; var sub = parent.FindPropertyRelative(subPropName) ?? parent.FindPropertyRelative("m_" + subPropName); if (sub != null && sub.propertyType == SerializedPropertyType.Float) sub.floatValue = ParamCoercion.CoerceFloat(value, sub.floatValue); } private static Component SwapPipelineComponent(GameObject go, string stage, string newTypeName) { var newType = CameraHelpers.ResolveComponentType(newTypeName); if (newType == null) return null; // Remove existing component of same pipeline stage var cmCamera = go.GetComponent(CameraHelpers.CinemachineCameraType); if (cmCamera != null) { var existing = CameraHelpers.GetPipelineComponent(cmCamera, stage); if (existing != null && existing.GetType() != newType) Undo.DestroyObjectImmediate(existing); } // Add new component if not already present var comp = go.GetComponent(newType); if (comp == null) comp = Undo.AddComponent(go, newType); return comp; } private static void SetComponentProperties(Component component, JObject props, string[] skipKeys) { if (component == null || props == null) return; var skipSet = new System.Collections.Generic.HashSet( skipKeys, StringComparer.OrdinalIgnoreCase); Undo.RecordObject(component, $"Configure {component.GetType().Name}"); foreach (var kv in props) { if (skipSet.Contains(kv.Key)) continue; ComponentOps.SetProperty(component, kv.Key, kv.Value, out _); } } #endregion } }