chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:49:17 +08:00
commit 7243d5823b
2201 changed files with 257291 additions and 0 deletions
@@ -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: