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

252 lines
10 KiB
C#

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
}
};
}
}
}