using System; using System.Collections.Generic; 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 CameraCreate { private static readonly Dictionary Presets = new(StringComparer.OrdinalIgnoreCase) { ["follow"] = ("CinemachineFollow", "CinemachineRotationComposer"), ["third_person"] = ("CinemachineThirdPersonFollow", "CinemachineRotationComposer"), ["freelook"] = ("CinemachineOrbitalFollow", "CinemachineRotationComposer"), ["dolly"] = ("CinemachineSplineDolly", "CinemachineRotationComposer"), ["static"] = (null, "CinemachineHardLookAt"), ["top_down"] = ("CinemachineFollow", null), ["side_scroller"] = ("CinemachinePositionComposer", null), }; internal static object CreateBasicCamera(JObject @params) { var props = CameraHelpers.ExtractProperties(@params) ?? new JObject(); string name = ParamCoercion.CoerceString(props["name"], null) ?? "Camera"; float fov = ParamCoercion.CoerceFloat(props["fieldOfView"], 60f); float near = ParamCoercion.CoerceFloat(props["nearClipPlane"], 0.3f); float far = ParamCoercion.CoerceFloat(props["farClipPlane"], 1000f); var go = new GameObject(name); Undo.RegisterCreatedObjectUndo(go, $"Create Camera '{name}'"); var cam = go.AddComponent(); cam.fieldOfView = fov; cam.nearClipPlane = near; cam.farClipPlane = far; // Position near follow target if provided string follow = ParamCoercion.CoerceString(props["follow"], null); if (follow != null) { var target = CameraHelpers.ResolveGameObjectRef(follow); if (target != null) go.transform.position = target.transform.position + new Vector3(0, 5, -10); } // Look at target if provided string lookAt = ParamCoercion.CoerceString(props["lookAt"] ?? props["look_at"], null); if (lookAt != null) { var target = CameraHelpers.ResolveGameObjectRef(lookAt); if (target != null) go.transform.LookAt(target.transform); } CameraHelpers.MarkDirty(go); return new { success = true, message = $"Created basic Camera '{name}' (Cinemachine not installed — using Unity Camera).", data = new { instanceID = go.GetInstanceIDCompat(), cinemachine = false, hint = "Install com.unity.cinemachine for presets, blending, and virtual camera features." } }; } internal static object CreateCinemachineCamera(JObject @params) { var props = CameraHelpers.ExtractProperties(@params) ?? new JObject(); string name = ParamCoercion.CoerceString(props["name"], null) ?? "CM Camera"; string preset = ParamCoercion.CoerceString(props["preset"], null) ?? "follow"; int priority = ParamCoercion.CoerceInt(props["priority"], 10); if (!Presets.TryGetValue(preset, out var presetDef)) { return new ErrorResponse( $"Unknown preset '{preset}'. Valid presets: {string.Join(", ", Presets.Keys)}."); } var go = new GameObject(name); Undo.RegisterCreatedObjectUndo(go, $"Create CinemachineCamera '{name}'"); // Add CinemachineCamera component var cmType = CameraHelpers.CinemachineCameraType; var cmCamera = go.AddComponent(cmType); // 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 { CameraHelpers.SetReflectionProperty(cmCamera, "Priority", priority); } } // Add Body component string bodyName = null; if (presetDef.body != null) { var bodyType = CameraHelpers.ResolveComponentType(presetDef.body); if (bodyType != null) { go.AddComponent(bodyType); bodyName = presetDef.body; } } // Add Aim component string aimName = null; if (presetDef.aim != null) { var aimType = CameraHelpers.ResolveComponentType(presetDef.aim); if (aimType != null) { go.AddComponent(aimType); aimName = presetDef.aim; } } // Set Follow target var followToken = props["follow"]; if (followToken != null && followToken.Type != JTokenType.Null) CameraHelpers.SetTransformTarget(cmCamera, "Follow", followToken); // Set LookAt target var lookAtToken = props["lookAt"] ?? props["look_at"]; if (lookAtToken != null && lookAtToken.Type != JTokenType.Null) CameraHelpers.SetTransformTarget(cmCamera, "LookAt", lookAtToken); CameraHelpers.MarkDirty(go); return new { success = true, message = $"Created CinemachineCamera '{name}' with preset '{preset}'.", data = new { instanceID = go.GetInstanceIDCompat(), cinemachine = true, preset, priority, body = bodyName, aim = aimName } }; } internal static object EnsureBrain(JObject @params) { var props = CameraHelpers.ExtractProperties(@params) ?? new JObject(); // Check if Brain already exists var existingBrain = CameraHelpers.FindBrain(); if (existingBrain != null) { return new { success = true, message = $"CinemachineBrain already exists on '{existingBrain.gameObject.name}'.", data = new { instanceID = existingBrain.gameObject.GetInstanceIDCompat(), alreadyExisted = true } }; } // Find target camera string cameraRef = ParamCoercion.CoerceString(props["camera"], null); UnityEngine.Camera cam; if (cameraRef != null) { var camGo = CameraHelpers.ResolveGameObjectRef(cameraRef); cam = camGo != null ? camGo.GetComponent() : null; } else { cam = CameraHelpers.FindMainCamera(); } if (cam == null) return new ErrorResponse("No Camera found to add CinemachineBrain to."); var brainType = CameraHelpers.CinemachineBrainType; Undo.RecordObject(cam.gameObject, "Add CinemachineBrain"); var brain = cam.gameObject.AddComponent(brainType); // Configure default blend if provided string blendStyle = ParamCoercion.CoerceString(props["defaultBlendStyle"] ?? props["default_blend_style"], null); float blendDuration = ParamCoercion.CoerceFloat(props["defaultBlendDuration"] ?? props["default_blend_duration"], -1f); if (blendStyle != null || blendDuration >= 0) { // Set via SerializedProperty for the DefaultBlend struct using var so = new SerializedObject(brain); var defaultBlendProp = so.FindProperty("DefaultBlend") ?? so.FindProperty("m_DefaultBlend"); if (defaultBlendProp != null) { if (blendStyle != null) { var styleProp = defaultBlendProp.FindPropertyRelative("Style") ?? defaultBlendProp.FindPropertyRelative("m_Style"); if (styleProp != null) { int idx = Array.FindIndex(styleProp.enumNames, n => n.Equals(blendStyle, StringComparison.OrdinalIgnoreCase)); if (idx >= 0) styleProp.enumValueIndex = idx; } } if (blendDuration >= 0) { var timeProp = defaultBlendProp.FindPropertyRelative("Time") ?? defaultBlendProp.FindPropertyRelative("m_Time"); if (timeProp != null) timeProp.floatValue = blendDuration; } so.ApplyModifiedProperties(); } } CameraHelpers.MarkDirty(cam.gameObject); return new { success = true, message = $"CinemachineBrain added to '{cam.gameObject.name}'.", data = new { instanceID = cam.gameObject.GetInstanceIDCompat(), alreadyExisted = false } }; } } }