123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- using System;
- using System.Linq;
- using System.Reflection;
- using UnityEngine;
- using UnityEditor;
- using LLM.Editor.Helper;
- using Newtonsoft.Json.Linq;
- using JetBrains.Annotations;
- using Object = UnityEngine.Object;
- namespace LLM.Editor.Commands
- {
- [Serializable]
- public class SetComponentValueParams
- {
- public string subjectIdentifier;
- public string componentName;
- public string memberName;
- public JToken value;
- }
- [UsedImplicitly]
- public class SetComponentValueCommand : ICommand
- {
- private readonly SetComponentValueParams _params;
- public SetComponentValueCommand(string jsonParams)
- {
- _params = jsonParams?.FromJson<SetComponentValueParams>();
- }
- public CommandOutcome Execute(Data.CommandContext context)
- {
- if (_params == null || string.IsNullOrEmpty(_params.subjectIdentifier) || string.IsNullOrEmpty(_params.componentName) || string.IsNullOrEmpty(_params.memberName))
- {
- context.ErrorMessage = "Invalid parameters. Subject, component, and member names are required.";
- return CommandOutcome.Error;
- }
- var targetObject = CommandUtility.ResolveIdentifier(context, _params.subjectIdentifier);
- if (!targetObject)
- {
- context.ErrorMessage = $"Could not find subject with logical name '{_params.subjectIdentifier}'.";
- return CommandOutcome.Error;
- }
- if (targetObject is not GameObject go)
- {
- context.ErrorMessage = $"The identified subject '{targetObject.name}' is not a GameObject.";
- return CommandOutcome.Error;
- }
- var targetComponent = go.GetComponent(_params.componentName);
- if (!targetComponent)
- {
- context.ErrorMessage = $"Could not find component '{_params.componentName}' on subject '{_params.subjectIdentifier}'.";
- return CommandOutcome.Error;
- }
- // Attempt 1: Use SerializedObject (the preferred Unity way)
- var serializedObject = new SerializedObject(targetComponent);
- var serializedProperty = serializedObject.FindProperty(_params.memberName);
- if (serializedProperty != null)
- {
- return SetValueWithSerializedProperty(context, serializedObject, serializedProperty);
- }
- // Attempt 2: Fallback to C# Reflection for public properties
- var propertyInfo = targetComponent.GetType().GetProperty(_params.memberName, BindingFlags.Public | BindingFlags.Instance);
- if (propertyInfo != null && propertyInfo.CanWrite)
- {
- return SetValueWithReflection(context, targetComponent, propertyInfo);
- }
- context.ErrorMessage = $"Could not find a writable serialized field or public property named '{_params.memberName}' on component '{_params.componentName}'.";
- return CommandOutcome.Error;
- }
- private CommandOutcome SetValueWithSerializedProperty(Data.CommandContext context, SerializedObject serializedObject, SerializedProperty property)
- {
- serializedObject.Update();
- try
- {
- switch (property.propertyType)
- {
- case SerializedPropertyType.ObjectReference:
- var objToAssign = GetObjectReferenceValue(context, property);
- if (objToAssign == null && _params.value.Type != JTokenType.Null) return CommandOutcome.Error;
- property.objectReferenceValue = objToAssign;
- break;
- case SerializedPropertyType.Float:
- property.floatValue = _params.value.Value<float>();
- break;
- case SerializedPropertyType.Integer:
- property.intValue = _params.value.Value<int>();
- break;
- case SerializedPropertyType.Boolean:
- property.boolValue = _params.value.Value<bool>();
- break;
- case SerializedPropertyType.String:
- property.stringValue = _params.value.Value<string>();
- break;
- case SerializedPropertyType.Vector3:
- property.vector3Value = _params.value.ToVector3();
- break;
- case SerializedPropertyType.Quaternion:
- property.quaternionValue = _params.value.ToQuaternion();
- break;
- default:
- context.ErrorMessage = $"The property type '{property.propertyType}' is not currently supported for setting values via SerializedObject.";
- return CommandOutcome.Error;
- }
- }
- catch (Exception e)
- {
- context.ErrorMessage = $"Failed to set value for '{_params.memberName}'. Error: {e.Message}";
- return CommandOutcome.Error;
- }
-
- serializedObject.ApplyModifiedProperties();
- Debug.Log($"[SetComponentValueCommand] Successfully set '{_params.memberName}' on '{_params.componentName}' using SerializedObject.");
- return CommandOutcome.Success;
- }
- private CommandOutcome SetValueWithReflection(Data.CommandContext context, Component target, PropertyInfo propertyInfo)
- {
- Undo.RecordObject(target, $"Set {propertyInfo.Name}");
- try
- {
- object valueToSet;
- var targetType = propertyInfo.PropertyType;
- if (targetType == typeof(Vector3)) valueToSet = _params.value.ToVector3();
- else if (targetType == typeof(Quaternion)) valueToSet = _params.value.ToQuaternion();
- else if (targetType.IsSubclassOf(typeof(Object)) || targetType == typeof(Object))
- {
- valueToSet = GetObjectReferenceValue(context, null);
- if (valueToSet == null && _params.value.Type != JTokenType.Null) return CommandOutcome.Error;
- }
- else
- {
- valueToSet = Convert.ChangeType(_params.value.ToObject(targetType), targetType);
- }
-
- propertyInfo.SetValue(target, valueToSet);
- }
- catch (Exception e)
- {
- context.ErrorMessage = $"Failed to set value for '{_params.memberName}' using Reflection. Error: {e.Message}";
- return CommandOutcome.Error;
- }
-
- EditorUtility.SetDirty(target);
- Debug.Log($"[SetComponentValueCommand] Successfully set '{_params.memberName}' on '{_params.componentName}' using Reflection.");
- return CommandOutcome.Success;
- }
- private Object GetObjectReferenceValue(Data.CommandContext context, SerializedProperty property)
- {
- var referenceData = _params.value.ToObject<ObjectReferenceValue>();
- if (referenceData == null)
- {
- if (_params.value.Type == JTokenType.Null) return null;
- context.ErrorMessage = "The 'value' for an ObjectReference must be an object with an 'identifier' or be null.";
- return null;
- }
- var baseObject = CommandUtility.ResolveIdentifier(context, referenceData.identifier);
- if (!baseObject)
- {
- context.ErrorMessage = $"Could not resolve the base identifier '{referenceData.identifier}' for the object reference.";
- return null;
- }
- Object objectToAssign = baseObject;
- if (baseObject is GameObject go && !string.IsNullOrEmpty(referenceData.childPath))
- {
- var childTransform = go.transform.Find(referenceData.childPath);
- if (!childTransform)
- {
- context.ErrorMessage = $"Could not find child '{referenceData.childPath}' on object '{baseObject.name}'.";
- return null;
- }
- objectToAssign = childTransform.gameObject;
-
- var componentType = GetTypeFromProperty(property);
- if (componentType != null && childTransform.TryGetComponent(componentType, out var component))
- {
- objectToAssign = component;
- }
- }
-
- return objectToAssign;
- }
-
- private static Type GetTypeFromProperty(SerializedProperty property)
- {
- if (property == null) return null;
- var typeString = property.type;
- var startIndex = typeString.IndexOf('<');
- var endIndex = typeString.IndexOf('>');
- if (startIndex == -1 || endIndex == -1) return null;
- var managedTypeName = typeString.Substring(startIndex + 1, endIndex - startIndex - 1).Trim();
- return GetTypeByName(managedTypeName);
- }
- private static Type GetTypeByName(string typeName)
- {
- return AppDomain.CurrentDomain.GetAssemblies()
- .Select(assembly => assembly.GetType(typeName, false))
- .FirstOrDefault(type => type != null);
- }
-
- [Serializable]
- private class ObjectReferenceValue
- {
- public string identifier;
- public string childPath;
- }
- }
- }
|