Problem: I want to store a script field (in particular numeric values (floats) and bools) in a similar way one would store a path for setting a SerializedProperty. I want to then at run time set the value of this field. SerializedProperty’s aren’t suitable, because they’re only available to the Unity Editor
What I have tried: SerializedObjects/SerializedPropertys. I have no problem doing this in the editor.
I think this problem is very similar to what the Animation window/animations solve. I want to take a state, save it, then at run time apply (possibly with modifications) the state. Animations do this by saving keyframes into .anim files, then somehow is able to apply them at runtime (possibly with modification from the interpolation based on the various curve information that was saved). How do I do what animations do? I do not want to set up animations for everything (this is not an option), I need to do this programmatically.
This is done using reflection. A built-in solution for that is UnityEvent:
using UnityEngine;
using UnityEngine.Events;
public class TestScript : MonoBehaviour {
public UnityEvent myEvent;
private void Update() {
if (Input.GetKeyDown(KeyCode.Space))
myEvent.Invoke();
}
}
UnityEvent has a drawer that you can set values in. You can also edit the calls through script, though it’s inconvenient.
I made a similar thing a while back that should work better, but I haven’t looked at it for a year. It’s here.
Ah, sorry I think I might have been unclear by “script field”. Your solution is for assigning a script, correct? What I’m interested in is assigning the values to any kind of field (float, bool, string, etc). Like this:
(I haven’t tested this code (I’m unsure the path “transform.x” would work), but I’ve done something similar that works in editor.) What I’m looking to do is set the values of any script field at runtime, similar to how the animation system seems to do.
Thanks, I was able to achieve this with Reflection. Your SerializedPropertyHelper.cs script helped a lot. I hobbled together this piece of code (this StackOverflow answer also helped (was kind of hoping there’d be a built-in way to avoid doing the string splitting)):
/// <summary>
/// See Jon Skeet's answer to this question for dealing with compounded properties: https://stackoverflow.com/a/12294308
/// </summary>
public static void SetCompoundedAttribute(string compoundedAttribute, object target, object value,
BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)
{
BindingFlags propertyBindingFLags = bindingFlags | BindingFlags.IgnoreCase;
string[] parts = compoundedAttribute.Split('.');
for(int i = 0; i < parts.Length - 1; i++)
{
Type classType = target.GetType();
FieldInfo fieldInfoToGet = classType.GetField(parts[i], bindingFlags);
if (fieldInfoToGet != null)
{
target = fieldInfoToGet.GetValue(target);
}
else
{
PropertyInfo propertyInfoToGet = classType.GetProperty(parts[i], propertyBindingFLags);
if(propertyInfoToGet != null)
{
target = propertyInfoToGet.GetValue(target);
}
else
{
Debug.LogError(string.Join(".", parts, 0, i + 1) + " is neither a field nor property");
}
}
}
Type targetClassType = target.GetType();
FieldInfo fieldInfoToSet = targetClassType.GetField(parts[parts.Length - 1], bindingFlags);
if (fieldInfoToSet != null)
{
// See this in case we need to handle nullable types: http://technico.qnownow.com/how-to-set-property-value-using-reflection-in-c/
fieldInfoToSet.SetValue(target, Convert.ChangeType(value, fieldInfoToSet.FieldType));
}
else
{
PropertyInfo propertyInfoToSet = targetClassType.GetProperty(parts[parts.Length - 1], propertyBindingFLags);
if(propertyInfoToSet != null)
{
propertyInfoToSet.SetValue(target, Convert.ChangeType(value, propertyInfoToSet.PropertyType));
}
else
{
Debug.LogError(compoundedAttribute + " is neither a field nor property");
}
}
}
A bit messy right now. Getting the correct component on the correct gameobject can be done using more string splitting, GetComponent, and checking various game object’s names. Interestingly, given the way (from how I’ve seen) Unity works, the Animation system’s limitations of only allowing for only unique names on children game objects, and only using one (the first) component on a game object, are not currently technical limitations. I can understand why it wouldn’t be allowed for design/maintenance/performance/some external reasons, but it’s neat that it could be easily overcome technically.