chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,412 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a26286aeede4949844309a8a952b2b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,315 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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 CameraControl
|
||||
{
|
||||
internal static object ListCameras(JObject @params)
|
||||
{
|
||||
var unityCameras = UnityFindObjectsCompat.FindAll<UnityEngine.Camera>();
|
||||
var cameraList = new List<object>();
|
||||
var unityCamList = new List<object>();
|
||||
|
||||
// Cinemachine cameras
|
||||
if (CameraHelpers.HasCinemachine)
|
||||
{
|
||||
var cmType = CameraHelpers.CinemachineCameraType;
|
||||
var allCm = UnityFindObjectsCompat.FindAll(cmType);
|
||||
foreach (Component cm in allCm)
|
||||
{
|
||||
var follow = CameraHelpers.GetReflectionProperty(cm, "Follow") as Transform;
|
||||
var lookAt = CameraHelpers.GetReflectionProperty(cm, "LookAt") as Transform;
|
||||
var isLive = CameraHelpers.GetReflectionProperty(cm, "IsLive");
|
||||
var priority = CameraHelpers.ReadCinemachinePriority(cm);
|
||||
|
||||
var body = CameraHelpers.GetPipelineComponent(cm, "Body");
|
||||
var aim = CameraHelpers.GetPipelineComponent(cm, "Aim");
|
||||
var noise = CameraHelpers.GetPipelineComponent(cm, "Noise");
|
||||
|
||||
// Collect extensions
|
||||
var extensions = new List<string>();
|
||||
var cmExtBaseType = cm.GetType().Assembly.GetType("Unity.Cinemachine.CinemachineExtension");
|
||||
if (cmExtBaseType != null)
|
||||
{
|
||||
foreach (var comp in cm.gameObject.GetComponents(cmExtBaseType))
|
||||
{
|
||||
if (comp != null)
|
||||
extensions.Add(comp.GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
cameraList.Add(new
|
||||
{
|
||||
instanceID = cm.gameObject.GetInstanceIDCompat(),
|
||||
name = cm.gameObject.name,
|
||||
isLive = isLive is bool b && b,
|
||||
priority,
|
||||
follow = follow != null ? new { name = follow.gameObject.name, instanceID = follow.gameObject.GetInstanceIDCompat() } : null,
|
||||
lookAt = lookAt != null ? new { name = lookAt.gameObject.name, instanceID = lookAt.gameObject.GetInstanceIDCompat() } : null,
|
||||
body = body?.GetType().Name,
|
||||
aim = aim?.GetType().Name,
|
||||
noise = noise?.GetType().Name,
|
||||
extensions
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Unity cameras
|
||||
foreach (var cam in unityCameras)
|
||||
{
|
||||
bool hasBrain = CameraHelpers.HasCinemachine &&
|
||||
cam.gameObject.GetComponent(CameraHelpers.CinemachineBrainType) != null;
|
||||
unityCamList.Add(new
|
||||
{
|
||||
instanceID = cam.gameObject.GetInstanceIDCompat(),
|
||||
name = cam.gameObject.name,
|
||||
depth = cam.depth,
|
||||
fieldOfView = cam.fieldOfView,
|
||||
hasBrain
|
||||
});
|
||||
}
|
||||
|
||||
// Brain info
|
||||
object brainInfo = null;
|
||||
if (CameraHelpers.HasCinemachine)
|
||||
{
|
||||
var brain = CameraHelpers.FindBrain();
|
||||
if (brain != null)
|
||||
{
|
||||
var activeCam = CameraHelpers.GetReflectionProperty(brain, "ActiveVirtualCamera");
|
||||
var isBlending = CameraHelpers.GetReflectionProperty(brain, "IsBlending");
|
||||
|
||||
string activeName = null;
|
||||
int? activeID = null;
|
||||
if (activeCam != null)
|
||||
{
|
||||
var nameProp = activeCam.GetType().GetProperty("Name");
|
||||
activeName = nameProp?.GetValue(activeCam) as string;
|
||||
|
||||
if (activeCam is Component activeComp)
|
||||
activeID = activeComp.gameObject.GetInstanceIDCompat();
|
||||
}
|
||||
|
||||
brainInfo = new
|
||||
{
|
||||
exists = true,
|
||||
gameObject = brain.gameObject.name,
|
||||
instanceID = brain.gameObject.GetInstanceIDCompat(),
|
||||
activeCameraName = activeName,
|
||||
activeCameraID = activeID,
|
||||
isBlending = isBlending is bool bl && bl
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
data = new
|
||||
{
|
||||
brain = brainInfo,
|
||||
cinemachineCameras = cameraList,
|
||||
unityCameras = unityCamList,
|
||||
cinemachineInstalled = CameraHelpers.HasCinemachine
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
internal static object GetBrainStatus(JObject @params)
|
||||
{
|
||||
var brain = CameraHelpers.FindBrain();
|
||||
if (brain == null)
|
||||
return new ErrorResponse("No CinemachineBrain found in the scene.");
|
||||
|
||||
var activeCam = CameraHelpers.GetReflectionProperty(brain, "ActiveVirtualCamera");
|
||||
var isBlending = CameraHelpers.GetReflectionProperty(brain, "IsBlending");
|
||||
var activeBlend = CameraHelpers.GetReflectionProperty(brain, "ActiveBlend");
|
||||
|
||||
string activeName = null;
|
||||
int? activeID = null;
|
||||
if (activeCam != null)
|
||||
{
|
||||
var nameProp = activeCam.GetType().GetProperty("Name");
|
||||
activeName = nameProp?.GetValue(activeCam) as string;
|
||||
if (activeCam is Component comp)
|
||||
activeID = comp.gameObject.GetInstanceIDCompat();
|
||||
}
|
||||
|
||||
string blendDesc = null;
|
||||
if (activeBlend != null)
|
||||
{
|
||||
var descProp = activeBlend.GetType().GetProperty("Description");
|
||||
blendDesc = descProp?.GetValue(activeBlend) as string;
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
data = new
|
||||
{
|
||||
gameObject = brain.gameObject.name,
|
||||
instanceID = brain.gameObject.GetInstanceIDCompat(),
|
||||
activeCameraName = activeName,
|
||||
activeCameraID = activeID,
|
||||
isBlending = isBlending is bool b && b,
|
||||
blendDescription = blendDesc
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
internal static object SetBlend(JObject @params)
|
||||
{
|
||||
var brain = CameraHelpers.FindBrain();
|
||||
if (brain == null)
|
||||
return new ErrorResponse("No CinemachineBrain found. Use 'ensure_brain' first.");
|
||||
|
||||
var props = CameraHelpers.ExtractProperties(@params) ?? new JObject();
|
||||
Undo.RecordObject(brain, "Set Camera Blend");
|
||||
|
||||
using var so = new SerializedObject(brain);
|
||||
var defaultBlendProp = so.FindProperty("DefaultBlend") ?? so.FindProperty("m_DefaultBlend");
|
||||
if (defaultBlendProp == null)
|
||||
return new ErrorResponse("Could not find DefaultBlend property on CinemachineBrain.");
|
||||
|
||||
string style = ParamCoercion.CoerceString(props["style"], null);
|
||||
if (style != null)
|
||||
{
|
||||
var styleProp = defaultBlendProp.FindPropertyRelative("Style")
|
||||
?? defaultBlendProp.FindPropertyRelative("m_Style");
|
||||
if (styleProp != null && styleProp.propertyType == SerializedPropertyType.Enum)
|
||||
{
|
||||
// Try to parse the style enum
|
||||
var enumNames = styleProp.enumNames;
|
||||
int idx = Array.FindIndex(enumNames, n => n.Equals(style, StringComparison.OrdinalIgnoreCase));
|
||||
if (idx >= 0)
|
||||
styleProp.enumValueIndex = idx;
|
||||
}
|
||||
}
|
||||
|
||||
float duration = ParamCoercion.CoerceFloat(props["duration"], -1f);
|
||||
if (duration >= 0)
|
||||
{
|
||||
var timeProp = defaultBlendProp.FindPropertyRelative("Time")
|
||||
?? defaultBlendProp.FindPropertyRelative("m_Time");
|
||||
if (timeProp != null)
|
||||
timeProp.floatValue = duration;
|
||||
}
|
||||
|
||||
so.ApplyModifiedProperties();
|
||||
CameraHelpers.MarkDirty(brain.gameObject);
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
message = "Default blend configured on CinemachineBrain.",
|
||||
data = new { instanceID = brain.gameObject.GetInstanceIDCompat() }
|
||||
};
|
||||
}
|
||||
|
||||
private static int _overrideId = -1;
|
||||
|
||||
internal static object ForceCamera(JObject @params)
|
||||
{
|
||||
var brain = CameraHelpers.FindBrain();
|
||||
if (brain == null)
|
||||
return new ErrorResponse("No CinemachineBrain found. Use 'ensure_brain' first.");
|
||||
|
||||
var cmCamera = CameraHelpers.FindCinemachineCamera(@params);
|
||||
if (cmCamera == null)
|
||||
return new ErrorResponse("Target CinemachineCamera not found.");
|
||||
|
||||
// Use SetCameraOverride via reflection
|
||||
var brainType = brain.GetType();
|
||||
var method = brainType.GetMethod("SetCameraOverride",
|
||||
BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
// Fallback: just set high priority
|
||||
CameraHelpers.SetReflectionProperty(cmCamera, "Priority", 999);
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
message = $"Set high priority on '{cmCamera.gameObject.name}' (SetCameraOverride not available).",
|
||||
data = new { instanceID = cmCamera.gameObject.GetInstanceIDCompat(), method = "priority" }
|
||||
};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// CM3 signature: SetCameraOverride(int overrideId, int priority,
|
||||
// ICinemachineCamera camA, ICinemachineCamera camB, float weightB, float deltaTime)
|
||||
// -1 for overrideId creates a new override; same cam for A+B with weight=1 = instant switch
|
||||
_overrideId = (int)method.Invoke(brain, new object[]
|
||||
{
|
||||
_overrideId >= 0 ? _overrideId : -1,
|
||||
999, // high priority to win over all others
|
||||
cmCamera, // camA (at weight=0)
|
||||
cmCamera, // camB (at weight=1) — same camera = no blend
|
||||
1f, // weightB = fully on camB
|
||||
-1f // deltaTime = use default
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Fallback
|
||||
CameraHelpers.SetReflectionProperty(cmCamera, "Priority", 999);
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
message = $"Forced via priority (override failed: {ex.Message}).",
|
||||
data = new { instanceID = cmCamera.gameObject.GetInstanceIDCompat(), method = "priority" }
|
||||
};
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
message = $"Camera overridden to '{cmCamera.gameObject.name}'.",
|
||||
data = new
|
||||
{
|
||||
instanceID = cmCamera.gameObject.GetInstanceIDCompat(),
|
||||
overrideId = _overrideId,
|
||||
method = "override"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
internal static object ReleaseOverride(JObject @params)
|
||||
{
|
||||
var brain = CameraHelpers.FindBrain();
|
||||
if (brain == null)
|
||||
return new ErrorResponse("No CinemachineBrain found.");
|
||||
|
||||
if (_overrideId < 0)
|
||||
return new { success = true, message = "No active camera override to release." };
|
||||
|
||||
var method = brain.GetType().GetMethod("ReleaseCameraOverride",
|
||||
BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
if (method != null)
|
||||
{
|
||||
method.Invoke(brain, new object[] { _overrideId });
|
||||
int releasedId = _overrideId;
|
||||
_overrideId = -1;
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
message = "Camera override released.",
|
||||
data = new { releasedOverrideId = releasedId }
|
||||
};
|
||||
}
|
||||
|
||||
_overrideId = -1;
|
||||
return new { success = true, message = "Override state cleared." };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6644251762504798895ef138ff182d29
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,251 @@
|
||||
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<string, (string body, string aim)> 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<UnityEngine.Camera>();
|
||||
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<UnityEngine.Camera>() : 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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a849a1ac03d245fe823e4c02b9e722d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using MCPForUnity.Runtime.Helpers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MCPForUnity.Editor.Tools.Cameras
|
||||
{
|
||||
internal static class CameraHelpers
|
||||
{
|
||||
private static bool? _hasCinemachine;
|
||||
private static Type _cmCameraType;
|
||||
private static Type _cmBrainType;
|
||||
|
||||
internal static bool HasCinemachine
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hasCinemachine == null)
|
||||
DetectCinemachine();
|
||||
return _hasCinemachine.Value;
|
||||
}
|
||||
}
|
||||
|
||||
internal static Type CinemachineCameraType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hasCinemachine == null)
|
||||
DetectCinemachine();
|
||||
return _cmCameraType;
|
||||
}
|
||||
}
|
||||
|
||||
internal static Type CinemachineBrainType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hasCinemachine == null)
|
||||
DetectCinemachine();
|
||||
return _cmBrainType;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DetectCinemachine()
|
||||
{
|
||||
_cmCameraType = UnityTypeResolver.ResolveComponent("CinemachineCamera");
|
||||
_cmBrainType = UnityTypeResolver.ResolveComponent("CinemachineBrain");
|
||||
_hasCinemachine = _cmCameraType != null && _cmBrainType != null;
|
||||
}
|
||||
|
||||
internal static string GetCinemachineVersion()
|
||||
{
|
||||
if (!HasCinemachine || _cmCameraType == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var assembly = _cmCameraType.Assembly;
|
||||
var version = assembly.GetName().Version;
|
||||
return version?.ToString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
internal static GameObject FindTargetGameObject(JObject @params)
|
||||
{
|
||||
var targetToken = @params["target"];
|
||||
if (targetToken == null)
|
||||
return null;
|
||||
|
||||
string searchMethod = ParamCoercion.CoerceString(
|
||||
@params["searchMethod"] ?? @params["search_method"], "by_name");
|
||||
|
||||
if (targetToken.Type == JTokenType.Integer)
|
||||
{
|
||||
int instanceId = targetToken.Value<int>();
|
||||
return GameObjectLookup.FindById(instanceId);
|
||||
}
|
||||
|
||||
string targetStr = targetToken.ToString();
|
||||
if (int.TryParse(targetStr, out int parsedId))
|
||||
{
|
||||
var byId = GameObjectLookup.FindById(parsedId);
|
||||
if (byId != null) return byId;
|
||||
}
|
||||
|
||||
return GameObjectLookup.FindByTarget(targetToken, searchMethod, true);
|
||||
}
|
||||
|
||||
internal static GameObject ResolveGameObjectRef(object reference)
|
||||
{
|
||||
if (reference == null) return null;
|
||||
|
||||
if (reference is JToken jt)
|
||||
{
|
||||
if (jt.Type == JTokenType.Integer)
|
||||
return GameObjectLookup.FindById(jt.Value<int>());
|
||||
if (jt.Type == JTokenType.String)
|
||||
{
|
||||
string str = jt.ToString();
|
||||
if (int.TryParse(str, out int id))
|
||||
{
|
||||
var byId = GameObjectLookup.FindById(id);
|
||||
if (byId != null) return byId;
|
||||
}
|
||||
return GameObjectLookup.FindByTarget(jt, "by_name", true);
|
||||
}
|
||||
}
|
||||
|
||||
if (reference is string s)
|
||||
{
|
||||
if (int.TryParse(s, out int id))
|
||||
{
|
||||
var byId = GameObjectLookup.FindById(id);
|
||||
if (byId != null) return byId;
|
||||
}
|
||||
var ids = GameObjectLookup.SearchGameObjects(
|
||||
GameObjectLookup.SearchMethod.ByName, s, includeInactive: true, maxResults: 1);
|
||||
return ids.Count > 0 ? GameObjectLookup.FindById(ids[0]) : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static Component FindCinemachineCamera(JObject @params)
|
||||
{
|
||||
if (!HasCinemachine) return null;
|
||||
var go = FindTargetGameObject(@params);
|
||||
return go != null ? go.GetComponent(CinemachineCameraType) : null;
|
||||
}
|
||||
|
||||
internal static Component FindBrain()
|
||||
{
|
||||
if (!HasCinemachine || _cmBrainType == null)
|
||||
return null;
|
||||
|
||||
return UnityFindObjectsCompat.FindAny(_cmBrainType) as Component;
|
||||
}
|
||||
|
||||
internal static UnityEngine.Camera FindMainCamera()
|
||||
{
|
||||
var main = UnityEngine.Camera.main;
|
||||
if (main != null) return main;
|
||||
|
||||
var allCams = UnityFindObjectsCompat.FindAll<UnityEngine.Camera>();
|
||||
return allCams.Length > 0 ? allCams[0] : null;
|
||||
}
|
||||
|
||||
internal static JObject ExtractProperties(JObject @params)
|
||||
{
|
||||
var props = @params["properties"] as JObject;
|
||||
if (props != null) return props;
|
||||
|
||||
var propsStr = ParamCoercion.CoerceString(@params["properties"], null);
|
||||
if (propsStr != null)
|
||||
{
|
||||
try { return JObject.Parse(propsStr); }
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static object GetReflectionProperty(Component component, string propertyName)
|
||||
{
|
||||
if (component == null) return null;
|
||||
var type = component.GetType();
|
||||
var prop = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
|
||||
return prop?.GetValue(component);
|
||||
}
|
||||
|
||||
/// <summary>Read priority int from a CinemachineCamera component via SerializedObject.</summary>
|
||||
internal static int ReadCinemachinePriority(Component cmCamera)
|
||||
{
|
||||
if (cmCamera == null) return 0;
|
||||
using var so = new SerializedObject(cmCamera);
|
||||
var priorityProp = so.FindProperty("Priority");
|
||||
if (priorityProp == null) return 0;
|
||||
var enabledProp = priorityProp.FindPropertyRelative("Enabled");
|
||||
var valueProp = priorityProp.FindPropertyRelative("m_Value");
|
||||
if (enabledProp != null && !enabledProp.boolValue) return 0;
|
||||
return valueProp?.intValue ?? 0;
|
||||
}
|
||||
|
||||
internal static bool SetReflectionProperty(Component component, string propertyName, object value)
|
||||
{
|
||||
if (component == null) return false;
|
||||
var type = component.GetType();
|
||||
var prop = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
|
||||
if (prop == null || !prop.CanWrite) return false;
|
||||
prop.SetValue(component, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void SetTransformTarget(Component cmCamera, string propertyName, JToken targetRef)
|
||||
{
|
||||
if (cmCamera == null) return;
|
||||
|
||||
if (targetRef == null || targetRef.Type == JTokenType.Null)
|
||||
{
|
||||
SetReflectionProperty(cmCamera, propertyName, null);
|
||||
return;
|
||||
}
|
||||
|
||||
var go = ResolveGameObjectRef(targetRef);
|
||||
if (go != null)
|
||||
SetReflectionProperty(cmCamera, propertyName, go.transform);
|
||||
}
|
||||
|
||||
internal static Type ResolveComponentType(string typeName)
|
||||
{
|
||||
return UnityTypeResolver.ResolveComponent(typeName);
|
||||
}
|
||||
|
||||
internal static Component GetPipelineComponent(Component cmCamera, string stageName)
|
||||
{
|
||||
if (cmCamera == null) return null;
|
||||
var type = cmCamera.GetType();
|
||||
|
||||
// CinemachineCamera.GetCinemachineComponent(CinemachineCore.Stage stage)
|
||||
var stageEnumType = type.Assembly.GetType("Unity.Cinemachine.CinemachineCore+Stage")
|
||||
?? type.Assembly.GetType("Unity.Cinemachine.CinemachineCore")?.GetNestedType("Stage");
|
||||
|
||||
if (stageEnumType == null) return null;
|
||||
|
||||
object stageEnum;
|
||||
try { stageEnum = Enum.Parse(stageEnumType, stageName, true); }
|
||||
catch { return null; }
|
||||
|
||||
var method = type.GetMethod("GetCinemachineComponent",
|
||||
BindingFlags.Public | BindingFlags.Instance,
|
||||
null, new[] { stageEnumType }, null);
|
||||
|
||||
if (method == null) return null;
|
||||
return method.Invoke(cmCamera, new[] { stageEnum }) as Component;
|
||||
}
|
||||
|
||||
internal static string GetFallbackSuggestion(string action)
|
||||
{
|
||||
return action switch
|
||||
{
|
||||
"set_body" or "set_aim" => "Use 'set_lens' and 'set_target' for basic camera configuration.",
|
||||
"set_blend" => "Without Cinemachine, switch cameras by enabling/disabling Camera components.",
|
||||
"set_noise" => "Camera shake without Cinemachine requires a custom script.",
|
||||
"ensure_brain" => "CinemachineBrain requires the Cinemachine package. Basic Camera does not need a Brain.",
|
||||
"get_brain_status" => "No CinemachineBrain available. Cinemachine package not installed.",
|
||||
_ => "Install Cinemachine via Window > Package Manager."
|
||||
};
|
||||
}
|
||||
|
||||
internal static void MarkDirty(GameObject go)
|
||||
{
|
||||
if (go == null) return;
|
||||
EditorUtility.SetDirty(go);
|
||||
var prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
|
||||
if (prefabStage != null)
|
||||
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(prefabStage.scene);
|
||||
else
|
||||
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(go.scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9664df00dcfa4a22b45ffa104bf29e46
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
|
||||
namespace MCPForUnity.Editor.Tools.Cameras
|
||||
{
|
||||
[McpForUnityTool("manage_camera", AutoRegister = false)]
|
||||
public static class ManageCamera
|
||||
{
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
if (@params == null)
|
||||
return new ErrorResponse("Parameters cannot be null.");
|
||||
|
||||
var p = new ToolParams(@params);
|
||||
string action = p.Get("action")?.ToLowerInvariant();
|
||||
|
||||
if (string.IsNullOrEmpty(action))
|
||||
return new ErrorResponse("'action' parameter is required.");
|
||||
|
||||
try
|
||||
{
|
||||
// Tier 1: Always-available actions (basic Camera fallback)
|
||||
switch (action)
|
||||
{
|
||||
case "ping":
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
message = CameraHelpers.HasCinemachine
|
||||
? "Cinemachine is available."
|
||||
: "Cinemachine not installed. Basic Camera operations available.",
|
||||
data = new
|
||||
{
|
||||
cinemachine = CameraHelpers.HasCinemachine,
|
||||
version = CameraHelpers.GetCinemachineVersion()
|
||||
}
|
||||
};
|
||||
|
||||
case "create_camera":
|
||||
return CameraHelpers.HasCinemachine
|
||||
? CameraCreate.CreateCinemachineCamera(@params)
|
||||
: CameraCreate.CreateBasicCamera(@params);
|
||||
|
||||
case "set_target":
|
||||
return CameraHelpers.HasCinemachine
|
||||
? CameraConfigure.SetCinemachineTarget(@params)
|
||||
: CameraConfigure.SetBasicCameraTarget(@params);
|
||||
|
||||
case "set_lens":
|
||||
return CameraHelpers.HasCinemachine
|
||||
? CameraConfigure.SetCinemachineLens(@params)
|
||||
: CameraConfigure.SetBasicCameraLens(@params);
|
||||
|
||||
case "set_priority":
|
||||
return CameraHelpers.HasCinemachine
|
||||
? CameraConfigure.SetCinemachinePriority(@params)
|
||||
: CameraConfigure.SetBasicCameraPriority(@params);
|
||||
|
||||
case "list_cameras":
|
||||
return CameraControl.ListCameras(@params);
|
||||
|
||||
case "screenshot":
|
||||
case "screenshot_multiview":
|
||||
{
|
||||
// Delegate to ManageScene's screenshot infrastructure
|
||||
var shotParams = new JObject(@params);
|
||||
shotParams["action"] = "screenshot";
|
||||
if (action == "screenshot_multiview")
|
||||
{
|
||||
shotParams["batch"] = "surround";
|
||||
shotParams["includeImage"] = true;
|
||||
}
|
||||
return ManageScene.HandleCommand(shotParams);
|
||||
}
|
||||
}
|
||||
|
||||
// Tier 2: Cinemachine-only actions
|
||||
if (!CameraHelpers.HasCinemachine)
|
||||
{
|
||||
return new ErrorResponse(
|
||||
$"Action '{action}' requires the Cinemachine package (com.unity.cinemachine). "
|
||||
+ CameraHelpers.GetFallbackSuggestion(action));
|
||||
}
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case "ensure_brain":
|
||||
return CameraCreate.EnsureBrain(@params);
|
||||
|
||||
case "get_brain_status":
|
||||
return CameraControl.GetBrainStatus(@params);
|
||||
|
||||
case "set_body":
|
||||
return CameraConfigure.SetBody(@params);
|
||||
|
||||
case "set_aim":
|
||||
return CameraConfigure.SetAim(@params);
|
||||
|
||||
case "set_noise":
|
||||
return CameraConfigure.SetNoise(@params);
|
||||
|
||||
case "add_extension":
|
||||
return CameraConfigure.AddExtension(@params);
|
||||
|
||||
case "remove_extension":
|
||||
return CameraConfigure.RemoveExtension(@params);
|
||||
|
||||
case "set_blend":
|
||||
return CameraControl.SetBlend(@params);
|
||||
|
||||
case "force_camera":
|
||||
return CameraControl.ForceCamera(@params);
|
||||
|
||||
case "release_override":
|
||||
return CameraControl.ReleaseOverride(@params);
|
||||
|
||||
default:
|
||||
return new ErrorResponse(
|
||||
$"Unknown action: '{action}'. Valid actions: ping, create_camera, set_target, "
|
||||
+ "set_lens, set_priority, list_cameras, screenshot, screenshot_multiview, "
|
||||
+ "ensure_brain, get_brain_status, "
|
||||
+ "set_body, set_aim, set_noise, add_extension, remove_extension, "
|
||||
+ "set_blend, force_camera, release_override.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
McpLog.Error($"[ManageCamera] Action '{action}' failed: {ex}");
|
||||
return new ErrorResponse($"Error in action '{action}': {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f61f34d01e54d5ba8e47acab546ed98
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user