Files
2026-07-13 12:49:17 +08:00

413 lines
17 KiB
C#

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<UnityEngine.Camera>();
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<UnityEngine.Camera>();
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<UnityEngine.Camera>();
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<string>());
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<string>(
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
}
}