706 lines
29 KiB
C#
706 lines
29 KiB
C#
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.Graphics
|
|
{
|
|
internal static class VolumeOps
|
|
{
|
|
// === volume_create ===
|
|
// Params: name (string), is_global (bool, default true), weight (float, default 1),
|
|
// priority (float, default 0), profile_path (string, optional - path to save VolumeProfile asset),
|
|
// effects (array of {type, ...params}, optional - effects to add immediately)
|
|
internal static object CreateVolume(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
string name = p.Get("name") ?? "Volume";
|
|
bool isGlobal = p.GetBool("is_global", true);
|
|
float weight = p.GetFloat("weight") ?? 1.0f;
|
|
float priority = p.GetFloat("priority") ?? 0f;
|
|
string profilePath = p.Get("profile_path");
|
|
if (!string.IsNullOrEmpty(profilePath))
|
|
{
|
|
if (!profilePath.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase) &&
|
|
!profilePath.StartsWith("Assets\\", StringComparison.OrdinalIgnoreCase))
|
|
profilePath = "Assets/" + profilePath;
|
|
if (!profilePath.EndsWith(".asset", StringComparison.OrdinalIgnoreCase))
|
|
profilePath += ".asset";
|
|
}
|
|
|
|
var go = new GameObject(name);
|
|
Undo.RegisterCreatedObjectUndo(go, $"Create Volume '{name}'");
|
|
|
|
// Add Volume component via reflection
|
|
var volumeComp = go.AddComponent(GraphicsHelpers.VolumeType);
|
|
|
|
// Set properties via reflection
|
|
SetProperty(volumeComp, "isGlobal", isGlobal);
|
|
SetProperty(volumeComp, "weight", weight);
|
|
SetProperty(volumeComp, "priority", priority);
|
|
|
|
// Create or load VolumeProfile
|
|
object profile;
|
|
if (!string.IsNullOrEmpty(profilePath))
|
|
{
|
|
// Load existing or create new profile asset
|
|
profile = AssetDatabase.LoadAssetAtPath(profilePath, GraphicsHelpers.VolumeProfileType);
|
|
if (profile == null)
|
|
{
|
|
profile = ScriptableObject.CreateInstance(GraphicsHelpers.VolumeProfileType);
|
|
// Ensure directory exists
|
|
var dir = System.IO.Path.GetDirectoryName(profilePath);
|
|
if (!string.IsNullOrEmpty(dir) && !System.IO.Directory.Exists(dir))
|
|
System.IO.Directory.CreateDirectory(dir);
|
|
AssetDatabase.CreateAsset((UnityEngine.Object)profile, profilePath);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Create embedded profile (not saved as asset)
|
|
profile = ScriptableObject.CreateInstance(GraphicsHelpers.VolumeProfileType);
|
|
}
|
|
|
|
// Assign profile (sharedProfile is a public field, handled by SetProperty's field fallback)
|
|
SetProperty(volumeComp, "sharedProfile", profile);
|
|
|
|
// Add initial effects if provided
|
|
var effectsToken = p.GetRaw("effects") as JArray;
|
|
var addedEffects = new List<string>();
|
|
if (effectsToken != null)
|
|
{
|
|
foreach (var effectDef in effectsToken)
|
|
{
|
|
if (effectDef is JObject effectObj)
|
|
{
|
|
string effectType = ParamCoercion.CoerceString(effectObj["type"], null);
|
|
if (string.IsNullOrEmpty(effectType)) continue;
|
|
|
|
var type = GraphicsHelpers.ResolveVolumeComponentType(effectType);
|
|
if (type == null) continue;
|
|
|
|
// profile.Add(type, true)
|
|
var addMethod = GraphicsHelpers.VolumeProfileType.GetMethod("Add",
|
|
new[] { typeof(Type), typeof(bool) });
|
|
if (addMethod != null)
|
|
{
|
|
var component = addMethod.Invoke(profile, new object[] { type, true });
|
|
if (component != null)
|
|
{
|
|
// Set parameters — support both nested {"parameters": {...}} and flat fields
|
|
var paramObj = effectObj["parameters"] as JObject;
|
|
if (paramObj != null)
|
|
{
|
|
foreach (var pp in paramObj.Properties())
|
|
SetVolumeParameter(component, pp.Name, pp.Value);
|
|
}
|
|
else
|
|
{
|
|
foreach (var prop in effectObj.Properties())
|
|
{
|
|
if (prop.Name == "type") continue;
|
|
SetVolumeParameter(component, prop.Name, prop.Value);
|
|
}
|
|
}
|
|
addedEffects.Add(effectType);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (profile is UnityEngine.Object profileObj)
|
|
EditorUtility.SetDirty(profileObj);
|
|
}
|
|
|
|
GraphicsHelpers.MarkDirty(volumeComp);
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Created {(isGlobal ? "global" : "local")} Volume '{name}'" +
|
|
(addedEffects.Count > 0 ? $" with effects: {string.Join(", ", addedEffects)}" : ""),
|
|
data = new
|
|
{
|
|
instanceID = go.GetInstanceIDCompat(),
|
|
isGlobal,
|
|
weight,
|
|
priority,
|
|
profilePath = profilePath ?? "(embedded)",
|
|
effects = addedEffects
|
|
}
|
|
};
|
|
}
|
|
|
|
// === volume_add_effect ===
|
|
// Params: target (string/int), effect (string - type name like "Bloom")
|
|
internal static object AddEffect(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
string effectName = p.Get("effect");
|
|
if (string.IsNullOrEmpty(effectName))
|
|
return new ErrorResponse("'effect' parameter is required (e.g., 'Bloom', 'Vignette').");
|
|
|
|
var volume = GraphicsHelpers.FindVolume(@params);
|
|
if (volume == null)
|
|
return new ErrorResponse("Volume not found. Specify 'target' (name or instance ID).");
|
|
|
|
var effectType = GraphicsHelpers.ResolveVolumeComponentType(effectName);
|
|
if (effectType == null)
|
|
{
|
|
var available = GraphicsHelpers.GetAvailableEffectTypes()
|
|
.Select(t => t.Name).ToList();
|
|
return new ErrorResponse(
|
|
$"Effect type '{effectName}' not found. Available: {string.Join(", ", available.Take(20))}");
|
|
}
|
|
|
|
var profile = GetProperty(volume, "sharedProfile");
|
|
if (profile == null)
|
|
return new ErrorResponse("Volume has no profile assigned.");
|
|
|
|
// Check if effect already exists
|
|
var components = GetProperty(profile, "components") as System.Collections.IList;
|
|
if (components != null)
|
|
{
|
|
foreach (var comp in components)
|
|
{
|
|
if (comp != null && comp.GetType() == effectType)
|
|
return new ErrorResponse($"Effect '{effectName}' already exists on this Volume. Use volume_set_effect to modify it.");
|
|
}
|
|
}
|
|
|
|
// profile.Add(effectType, true) -- 'true' means override all params
|
|
var addMethod = GraphicsHelpers.VolumeProfileType.GetMethod("Add",
|
|
new[] { typeof(Type), typeof(bool) });
|
|
if (addMethod == null)
|
|
return new ErrorResponse("Could not find VolumeProfile.Add method.");
|
|
|
|
var component = addMethod.Invoke(profile, new object[] { effectType, true });
|
|
if (component == null)
|
|
return new ErrorResponse($"Failed to add effect '{effectName}'.");
|
|
|
|
if (profile is UnityEngine.Object profileObj)
|
|
EditorUtility.SetDirty(profileObj);
|
|
GraphicsHelpers.MarkDirty(volume);
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Added '{effectName}' to Volume '{(volume as Component)?.gameObject.name}'.",
|
|
data = new { effect = effectName, volumeInstanceID = (volume as Component)?.gameObject.GetInstanceIDCompat() }
|
|
};
|
|
}
|
|
|
|
// === volume_set_effect ===
|
|
// Params: target (string/int), effect (string), parameters (dict of field->value)
|
|
internal static object SetEffect(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
string effectName = p.Get("effect");
|
|
if (string.IsNullOrEmpty(effectName))
|
|
return new ErrorResponse("'effect' parameter is required.");
|
|
|
|
var volume = GraphicsHelpers.FindVolume(@params);
|
|
if (volume == null)
|
|
return new ErrorResponse("Volume not found. Specify 'target'.");
|
|
|
|
var profile = GetProperty(volume, "sharedProfile");
|
|
if (profile == null)
|
|
return new ErrorResponse("Volume has no profile assigned.");
|
|
|
|
var effectType = GraphicsHelpers.ResolveVolumeComponentType(effectName);
|
|
if (effectType == null)
|
|
return new ErrorResponse($"Effect type '{effectName}' not found.");
|
|
|
|
// Find the effect component in the profile
|
|
var components = GetProperty(profile, "components") as System.Collections.IList;
|
|
if (components == null)
|
|
return new ErrorResponse("Could not read profile components.");
|
|
|
|
object targetComponent = null;
|
|
foreach (var comp in components)
|
|
{
|
|
if (comp != null && comp.GetType() == effectType)
|
|
{
|
|
targetComponent = comp;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (targetComponent == null)
|
|
return new ErrorResponse($"Effect '{effectName}' not found on this Volume. Use volume_add_effect first.");
|
|
|
|
// Set parameters
|
|
var parameters = p.GetRaw("parameters") as JObject;
|
|
if (parameters == null)
|
|
return new ErrorResponse("'parameters' dict is required.");
|
|
|
|
var setParams = new List<string>();
|
|
var failedParams = new List<string>();
|
|
foreach (var prop in parameters.Properties())
|
|
{
|
|
if (SetVolumeParameter(targetComponent, prop.Name, prop.Value))
|
|
setParams.Add(prop.Name);
|
|
else
|
|
failedParams.Add(prop.Name);
|
|
}
|
|
|
|
if (profile is UnityEngine.Object profileObj)
|
|
EditorUtility.SetDirty(profileObj);
|
|
|
|
var msg = $"Set {setParams.Count} parameter(s) on '{effectName}'";
|
|
if (failedParams.Count > 0)
|
|
msg += $". Failed: {string.Join(", ", failedParams)}";
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = msg,
|
|
data = new { effect = effectName, set = setParams, failed = failedParams }
|
|
};
|
|
}
|
|
|
|
// === volume_remove_effect ===
|
|
// Params: target, effect
|
|
internal static object RemoveEffect(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
string effectName = p.Get("effect");
|
|
if (string.IsNullOrEmpty(effectName))
|
|
return new ErrorResponse("'effect' parameter is required.");
|
|
|
|
var volume = GraphicsHelpers.FindVolume(@params);
|
|
if (volume == null)
|
|
return new ErrorResponse("Volume not found.");
|
|
|
|
var effectType = GraphicsHelpers.ResolveVolumeComponentType(effectName);
|
|
if (effectType == null)
|
|
return new ErrorResponse($"Effect type '{effectName}' not found.");
|
|
|
|
var profile = GetProperty(volume, "sharedProfile");
|
|
if (profile == null)
|
|
return new ErrorResponse("Volume has no profile.");
|
|
|
|
// Check if effect exists before removing
|
|
bool found = false;
|
|
var components = GetProperty(profile, "components") as System.Collections.IList;
|
|
if (components != null)
|
|
{
|
|
foreach (var comp in components)
|
|
{
|
|
if (comp != null && comp.GetType() == effectType)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!found)
|
|
return new ErrorResponse($"Effect '{effectName}' not found on this Volume.");
|
|
|
|
var removeMethod = GraphicsHelpers.VolumeProfileType.GetMethod("Remove",
|
|
new[] { typeof(Type) });
|
|
if (removeMethod == null)
|
|
return new ErrorResponse("Could not find VolumeProfile.Remove method.");
|
|
|
|
removeMethod.Invoke(profile, new object[] { effectType });
|
|
|
|
if (profile is UnityEngine.Object profileObj)
|
|
EditorUtility.SetDirty(profileObj);
|
|
GraphicsHelpers.MarkDirty(volume);
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Removed '{effectName}' from Volume.",
|
|
data = new { effect = effectName }
|
|
};
|
|
}
|
|
|
|
// === volume_get_info ===
|
|
// Params: target (optional -- if omitted, returns info for all volumes)
|
|
internal static object GetInfo(JObject @params)
|
|
{
|
|
var volume = GraphicsHelpers.FindVolume(@params);
|
|
if (volume == null)
|
|
return new ErrorResponse("Volume not found.");
|
|
|
|
var info = BuildVolumeInfo(volume);
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Volume info for '{(volume as Component)?.gameObject.name}'.",
|
|
data = info
|
|
};
|
|
}
|
|
|
|
// === volume_set_properties ===
|
|
// Params: target, weight, priority, is_global, blend_distance
|
|
// OR properties dict with those keys
|
|
internal static object SetProperties(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
var volume = GraphicsHelpers.FindVolume(@params);
|
|
if (volume == null)
|
|
return new ErrorResponse("Volume not found.");
|
|
|
|
// Unpack "properties" dict into top-level params so callers can use either style
|
|
var propsDict = p.GetRaw("properties") as JObject;
|
|
if (propsDict != null)
|
|
{
|
|
foreach (var prop in propsDict.Properties())
|
|
{
|
|
if (@params[prop.Name] == null)
|
|
@params[prop.Name] = prop.Value;
|
|
}
|
|
p = new ToolParams(@params);
|
|
}
|
|
|
|
var changed = new List<string>();
|
|
|
|
var weight = p.GetFloat("weight");
|
|
if (weight.HasValue) { SetProperty(volume, "weight", weight.Value); changed.Add("weight"); }
|
|
|
|
var priority = p.GetFloat("priority");
|
|
if (priority.HasValue) { SetProperty(volume, "priority", priority.Value); changed.Add("priority"); }
|
|
|
|
if (p.Has("is_global")) { SetProperty(volume, "isGlobal", p.GetBool("is_global")); changed.Add("isGlobal"); }
|
|
|
|
var blendDist = p.GetFloat("blend_distance");
|
|
if (blendDist.HasValue) { SetProperty(volume, "blendDistance", blendDist.Value); changed.Add("blendDistance"); }
|
|
|
|
if (changed.Count == 0)
|
|
return new ErrorResponse("No properties specified. Use: weight, priority, is_global, blend_distance.");
|
|
|
|
GraphicsHelpers.MarkDirty(volume);
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Updated Volume properties: {string.Join(", ", changed)}",
|
|
data = new { changed }
|
|
};
|
|
}
|
|
|
|
// === volume_list_effects ===
|
|
// No params needed -- lists all available VolumeComponent types
|
|
internal static object ListEffects(JObject @params)
|
|
{
|
|
var types = GraphicsHelpers.GetAvailableEffectTypes();
|
|
var effectList = types.Select(t => new
|
|
{
|
|
name = t.Name,
|
|
fullName = t.FullName,
|
|
ns = t.Namespace
|
|
}).ToList();
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Found {effectList.Count} available volume effects.",
|
|
data = new { pipeline = GraphicsHelpers.GetPipelineName(), effects = effectList }
|
|
};
|
|
}
|
|
|
|
// === volume_create_profile ===
|
|
// Params: path (string -- asset path like "Assets/Settings/MyProfile.asset")
|
|
internal static object CreateProfile(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
string path = p.Get("path");
|
|
if (string.IsNullOrEmpty(path))
|
|
return new ErrorResponse("'path' parameter is required (e.g., 'Settings/MyProfile' or 'Assets/Settings/MyProfile.asset').");
|
|
|
|
// Auto-prepend Assets/ if missing (paths are relative to Assets/ by convention)
|
|
if (!path.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase) &&
|
|
!path.StartsWith("Assets\\", StringComparison.OrdinalIgnoreCase))
|
|
path = "Assets/" + path;
|
|
|
|
if (!path.EndsWith(".asset", StringComparison.OrdinalIgnoreCase))
|
|
path += ".asset";
|
|
|
|
// Ensure directory exists
|
|
var dir = System.IO.Path.GetDirectoryName(path);
|
|
if (!string.IsNullOrEmpty(dir) && !AssetDatabase.IsValidFolder(dir))
|
|
{
|
|
// Create folders recursively
|
|
var parts = dir.Replace("\\", "/").Split('/');
|
|
string current = parts[0];
|
|
for (int i = 1; i < parts.Length; i++)
|
|
{
|
|
string next = current + "/" + parts[i];
|
|
if (!AssetDatabase.IsValidFolder(next))
|
|
AssetDatabase.CreateFolder(current, parts[i]);
|
|
current = next;
|
|
}
|
|
}
|
|
|
|
var profile = ScriptableObject.CreateInstance(GraphicsHelpers.VolumeProfileType);
|
|
AssetDatabase.CreateAsset(profile, path);
|
|
AssetDatabase.SaveAssets();
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Created VolumeProfile at '{path}'.",
|
|
data = new { path }
|
|
};
|
|
}
|
|
|
|
// === ListVolumes (used by VolumesResource) ===
|
|
internal static object ListVolumes(JObject @params)
|
|
{
|
|
if (!GraphicsHelpers.HasVolumeSystem)
|
|
return new { success = true, message = "Volume system not available.", data = new { volumes = new List<object>() } };
|
|
|
|
var allVolumes = UnityFindObjectsCompat.FindAll(GraphicsHelpers.VolumeType);
|
|
var volumeList = new List<object>();
|
|
|
|
foreach (Component vol in allVolumes)
|
|
{
|
|
volumeList.Add(BuildVolumeInfo(vol));
|
|
}
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Found {volumeList.Count} volume(s).",
|
|
data = new { pipeline = GraphicsHelpers.GetPipelineName(), volumes = volumeList }
|
|
};
|
|
}
|
|
|
|
// --- Helper: Build info object for a single Volume ---
|
|
private static object BuildVolumeInfo(object volumeComponent)
|
|
{
|
|
var comp = volumeComponent as Component;
|
|
if (comp == null) return null;
|
|
|
|
bool isGlobal = GetPropertyValue<bool>(volumeComponent, "isGlobal", true);
|
|
float weight = GetPropertyValue<float>(volumeComponent, "weight", 1f);
|
|
float priority = GetPropertyValue<float>(volumeComponent, "priority", 0f);
|
|
float blendDistance = GetPropertyValue<float>(volumeComponent, "blendDistance", 0f);
|
|
|
|
var profile = GetProperty(volumeComponent, "sharedProfile");
|
|
string profileName = profile is UnityEngine.Object profileObj2 ? profileObj2.name : null;
|
|
string profilePath = profile is UnityEngine.Object po ? AssetDatabase.GetAssetPath(po) : null;
|
|
|
|
var effectsList = new List<object>();
|
|
if (profile != null)
|
|
{
|
|
var components = GetProperty(profile, "components") as System.Collections.IList;
|
|
if (components != null)
|
|
{
|
|
foreach (var effect in components)
|
|
{
|
|
if (effect == null) continue;
|
|
var effectType = effect.GetType();
|
|
bool active = GetPropertyValue<bool>(effect, "active", true);
|
|
|
|
// Collect overridden parameters
|
|
var overriddenParams = new List<string>();
|
|
foreach (var field in effectType.GetFields(BindingFlags.Public | BindingFlags.Instance))
|
|
{
|
|
var fieldValue = field.GetValue(effect);
|
|
if (fieldValue == null) continue;
|
|
var overrideProp = fieldValue.GetType().GetProperty("overrideState");
|
|
if (overrideProp != null)
|
|
{
|
|
bool overridden = (bool)overrideProp.GetValue(fieldValue);
|
|
if (overridden)
|
|
overriddenParams.Add(field.Name);
|
|
}
|
|
}
|
|
|
|
effectsList.Add(new
|
|
{
|
|
type = effectType.Name,
|
|
active,
|
|
overridden_params = overriddenParams
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return new
|
|
{
|
|
name = comp.gameObject.name,
|
|
instance_id = comp.gameObject.GetInstanceIDCompat(),
|
|
is_global = isGlobal,
|
|
weight,
|
|
priority,
|
|
blend_distance = blendDistance,
|
|
profile = profileName,
|
|
profile_path = profilePath ?? "",
|
|
effects = effectsList
|
|
};
|
|
}
|
|
|
|
// --- Helper: Set a VolumeParameter field value via reflection ---
|
|
// VolumeParameter<T> has: overrideState (bool), value (T)
|
|
internal static bool SetVolumeParameter(object component, string fieldName, JToken value)
|
|
{
|
|
if (component == null || string.IsNullOrEmpty(fieldName)) return false;
|
|
|
|
var field = component.GetType().GetField(fieldName,
|
|
BindingFlags.Public | BindingFlags.Instance);
|
|
if (field == null)
|
|
{
|
|
// Try camelCase conversion from snake_case
|
|
string camelCase = StringCaseUtility.ToCamelCase(fieldName);
|
|
field = component.GetType().GetField(camelCase,
|
|
BindingFlags.Public | BindingFlags.Instance);
|
|
}
|
|
if (field == null) return false;
|
|
|
|
var param = field.GetValue(component);
|
|
if (param == null) return false;
|
|
|
|
// Set value with type conversion, then enable override on success
|
|
var valueProp = param.GetType().GetProperty("value");
|
|
if (valueProp == null) return false;
|
|
|
|
try
|
|
{
|
|
object converted = ConvertToParameterType(value, valueProp.PropertyType);
|
|
valueProp.SetValue(param, converted);
|
|
|
|
var overrideProp = param.GetType().GetProperty("overrideState");
|
|
if (overrideProp != null)
|
|
overrideProp.SetValue(param, true);
|
|
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
McpLog.Warn($"[VolumeOps] Failed to set '{fieldName}': {ex.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// --- Helper: Convert JToken to target parameter type ---
|
|
private static object ConvertToParameterType(JToken value, Type targetType)
|
|
{
|
|
if (value == null || value.Type == JTokenType.Null) return null;
|
|
|
|
// Handle Color
|
|
if (targetType == typeof(Color))
|
|
{
|
|
if (value is JArray arr && arr.Count >= 3)
|
|
{
|
|
float r = arr[0].Value<float>();
|
|
float g = arr[1].Value<float>();
|
|
float b = arr[2].Value<float>();
|
|
float a = arr.Count >= 4 ? arr[3].Value<float>() : 1f;
|
|
return new Color(r, g, b, a);
|
|
}
|
|
// Try hex string
|
|
if (value.Type == JTokenType.String)
|
|
{
|
|
if (ColorUtility.TryParseHtmlString(value.ToString(), out Color c))
|
|
return c;
|
|
}
|
|
}
|
|
|
|
// Handle Vector2
|
|
if (targetType == typeof(Vector2))
|
|
{
|
|
if (value is JArray arr && arr.Count >= 2)
|
|
return new Vector2(arr[0].Value<float>(), arr[1].Value<float>());
|
|
}
|
|
|
|
// Handle Vector3
|
|
if (targetType == typeof(Vector3))
|
|
{
|
|
if (value is JArray arr && arr.Count >= 3)
|
|
return new Vector3(arr[0].Value<float>(), arr[1].Value<float>(), arr[2].Value<float>());
|
|
}
|
|
|
|
// Handle Vector4
|
|
if (targetType == typeof(Vector4))
|
|
{
|
|
if (value is JArray arr && arr.Count >= 4)
|
|
return new Vector4(arr[0].Value<float>(), arr[1].Value<float>(),
|
|
arr[2].Value<float>(), arr[3].Value<float>());
|
|
}
|
|
|
|
// Handle enums
|
|
if (targetType.IsEnum)
|
|
{
|
|
string str = value.ToString();
|
|
if (Enum.TryParse(targetType, str, true, out object enumVal))
|
|
return enumVal;
|
|
// Try as int
|
|
if (int.TryParse(str, out int intVal))
|
|
return Enum.ToObject(targetType, intVal);
|
|
}
|
|
|
|
// Handle bool
|
|
if (targetType == typeof(bool))
|
|
return ParamCoercion.CoerceBool(value, false);
|
|
|
|
// Handle float
|
|
if (targetType == typeof(float))
|
|
return ParamCoercion.CoerceFloat(value, 0f);
|
|
|
|
// Handle int
|
|
if (targetType == typeof(int))
|
|
return ParamCoercion.CoerceInt(value, 0);
|
|
|
|
// Handle Texture2D (by asset path)
|
|
if (targetType == typeof(Texture2D) || targetType == typeof(Texture))
|
|
{
|
|
string path = value.ToString();
|
|
return AssetDatabase.LoadAssetAtPath<Texture2D>(path);
|
|
}
|
|
|
|
// Fallback: try Convert
|
|
try
|
|
{
|
|
return Convert.ChangeType(value.ToObject<object>(), targetType);
|
|
}
|
|
catch
|
|
{
|
|
return value.ToObject<object>();
|
|
}
|
|
}
|
|
|
|
// --- Reflection helpers (with field fallback for Volume.sharedProfile etc.) ---
|
|
private static object GetProperty(object obj, string name)
|
|
{
|
|
if (obj == null) return null;
|
|
var prop = obj.GetType().GetProperty(name,
|
|
BindingFlags.Public | BindingFlags.Instance);
|
|
if (prop != null) return prop.GetValue(obj);
|
|
// Fallback: try as a field (e.g., Volume.sharedProfile is a public field, not a property)
|
|
var field = obj.GetType().GetField(name,
|
|
BindingFlags.Public | BindingFlags.Instance);
|
|
return field?.GetValue(obj);
|
|
}
|
|
|
|
private static T GetPropertyValue<T>(object obj, string name, T defaultValue)
|
|
{
|
|
var val = GetProperty(obj, name);
|
|
if (val is T typed) return typed;
|
|
return defaultValue;
|
|
}
|
|
|
|
private static void SetProperty(object obj, string name, object value)
|
|
{
|
|
if (obj == null) return;
|
|
var prop = obj.GetType().GetProperty(name,
|
|
BindingFlags.Public | BindingFlags.Instance);
|
|
if (prop != null && prop.CanWrite)
|
|
{
|
|
prop.SetValue(obj, value);
|
|
return;
|
|
}
|
|
// Fallback: try as a field (e.g., Volume.sharedProfile is a public field, not a property)
|
|
var field = obj.GetType().GetField(name,
|
|
BindingFlags.Public | BindingFlags.Instance);
|
|
field?.SetValue(obj, value);
|
|
}
|
|
}
|
|
}
|