- Home /
How to select an int/float variable from another script in the inspector?
I have an input field that lets the user change the value of a variable of another script. I want the placeholder text (for if the input field is empty or inactive) to just display the current value of that variable.
I want to be able to select an object's variable as you would when setting up an event, by dragging and dropping an object into a field and selecting the variable from a drop-down menu. I don't need to know the type of the script. I only need to know that the variable is a float or an int. Then my input field will have a reference to that specified float value and can set the placeholder text from that.
Edit: Basically, I want to have a public float variable on my input field, and instead of typing in a value as you normally would, I want to drag and drop a reference to a float variable from a different script, without needing to know the type of that different script (or the name of the variable) beforehand.
Do you mean like a scriptable object? https://docs.unity3d.com/$$anonymous$$anual/class-ScriptableObject.html
You can create a scritable object with public float ID, and then you can scriptable object reference to the script you are talking about.
This is what scriptableobject script would look like:
public class ExampleClass : ScriptableObject
{
public float ID;
}
Then in the script you want to reference the scriptable object, add these variables:
public ExampleClass reference;
float ID;
This will create the drag and drop feature you are looking for in the inspector, then wherever in runtime you are trying to get the value of this drag and drop value, you would reference it there; in this example I will show you how to do it in Start()
public void Start()
{
ID = reference.ID;
}
Is this what you would be looking for?
Not quite. The script that references ExampleClass should not need to know the type ExampleClass. It should only need to know that it is a GameObject or Component. The referencing script should just allow you to pick from a list of properties/fields on any script that you drag in to it which are of type float.
This is cause my input field works for any kind of script. It has an event that it invokes when I submit the input text which sends the input to a script that I add from a drop-down menu. I only need one small method in my receiving script which can convert the input from a string to a float, and then assign that float to the property/field that I want my input field to control.
All I want to do is take the current value of the property/field that my input field is controlling and display that value in the input field if the user isn't currently typing in a new value.
So let me understand. It sounds like this is an interactive part of your project and it requires user input, let me know if this is the process:
One script captures user input
Another script contains the answer value
If the user input does not match the answer value, show the user what input they should be pressing
If that is what you are trying to do, provide your user input script and I can create you a script that will have a list of floats that you can input in the inspector, or I can code it so it creates random float values for you.
One thing though, float values are not great values to compare with, an int value would be much more appropriate; if the value you are trying to compare is a float you can round to an int that way you won't run into bugs with Unity not being able to compare exactly.
Is this link between the custom input field and the generic object going to last during runtime too, only in editor mode, or only when the editor with the custom input field is open?
Answer by LeonardSchoyen · Jul 15, 2019 at 05:03 PM
This can be done by manually creating a serialized object from a given generic mono-behavior. This approach requires quite some editor scripting, which can be finicky and weird. The core idea of the method is that you can create your own serialized object from components, and potentially scriptable objects, that can be manipulated like any other custom editor would. The usage of EditorGUILayout.Popup(...) and EditorGUILayout.PropertyField(...) is just as an example to show how you can display things.
A big weakness with the code as it stands now is that nothing is saved to the actual NumberFIeldChange component, so if you hide the editor by going to another game object, the link will be gone. This can be fixed by saving the values used in the custom editor to the actual object. You can look into the static Undo class if you need this functionality.
Example classes:
ComponentWithNumberFields.cs
using UnityEngine;
public class ComponentWithNumberFields : MonoBehaviour
{
[SerializeField]
private float floatField = 0;
[SerializeField]
private int intField = 0;
}
ComponentWithoutNumberFIelds.cs
using UnityEngine;
public class ComponentWithoutNumberFields : MonoBehaviour
{
[SerializeField]
private string stringField = "Hello Unity";
}
NumberFieldChanger.cs
using UnityEngine;
public class NumberFieldChanger : MonoBehaviour { }
NumberFieldChanger_Editor.cs
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(NumberFieldChanger))]
public class NumberFieldChanger_Editor : Editor
{
private string fieldName;
private List<string> fieldNames;
private MonoBehaviour targetScript;
private SerializedProperty targetSerializedProperty;
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
// Field to select generic script component from scene.
MonoBehaviour newTargetScript = (MonoBehaviour)EditorGUILayout.ObjectField("Generic Object", this.targetScript, typeof(MonoBehaviour), true);
if (newTargetScript != this.targetScript) {
this.fieldName = null;
this.targetScript = newTargetScript;
}
if (this.targetScript != null) {
// Creating SerializedObject from selected component.
// Updating serialized object is not necessary as it is created the same frame.
SerializedObject targetSerializedObject = new SerializedObject(this.targetScript);
// Lists for relevant fields found:
this.fieldNames = new List<string>();
List<SerializedProperty> serializedProperties = new List<SerializedProperty>();
// Goes through all properties of the selected object.
// Serialized properties have iterator functionality built in.
SerializedProperty serializedProperty = targetSerializedObject.GetIterator();
serializedProperty.Next(true);
do {
// Runs code block if property is of correct type.
switch (serializedProperty.propertyType) {
case SerializedPropertyType.Float:
case SerializedPropertyType.Integer:
// Saves values found
this.fieldNames.Add(serializedProperty.name);
break;
default:
break;
}
} while (serializedProperty.Next(false));
// Removes two hidden int options one probably shouldn't mess with
this.fieldNames.RemoveAt(0);
this.fieldNames.RemoveAt(0);
// Ensures that there exist fields to choose from
if (this.fieldNames.Count != 0) {
// Selection of field
int oldIndex = this.fieldNames.Contains(this.fieldName) ? this.fieldNames.IndexOf(this.fieldName) : 0;
int newIndex = EditorGUILayout.Popup(oldIndex, this.fieldNames.ToArray());
this.fieldName = this.fieldNames[newIndex];
this.targetSerializedProperty = targetSerializedObject.FindProperty(this.fieldName);
} else {
EditorGUILayout.HelpBox("Given component has no float or int fields", MessageType.None);
this.targetSerializedProperty = null;
}
if (this.targetSerializedProperty != null) {
// Displays a field with no label with the selected field in the target script.
EditorGUILayout.PropertyField(this.targetSerializedProperty, GUIContent.none);
// Makes changes to target object.
targetSerializedObject.ApplyModifiedProperties();
}
}
}
}
Answer by tuf91497 · Jul 15, 2019 at 04:40 PM
I realized that I could simply add an event to my input field that assigns the placeholder text from the input text whenever I hit submit... Now I just need a little script to get my starting value.
Somewhat unfortunately, before I realized this, I wrote this nice (But definitely imperfect) script that lets you drag and drop a GameObject in and then select from a list of float-type properties and fields and store the value of your selection locally. If anyone wants it, I will put it below (Obviously, use is at your own risk). It only works for float values, but you could fairly easily convert it to work with any type. But using this means that you only need to know the type FloatHolder and the field activeValue, and you can siphon any float from any other script into activeValue.
Edit: Wasn't saving values when played or when the editor view was moved. Now it should work better.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Reflection;
using System;
using UnityEditor;
public class FloatHolder : MonoBehaviour
{
public bool runAtAwake = true;
public bool runAtStart = true;
public bool runAtRuntime = true;
public float activeValue;
public GameObject obj;
[HideInInspector]
public int componentSelected;
[HideInInspector]
public int propertyOrFieldSelected;
[HideInInspector]
public string[] components;
[HideInInspector]
public string[] validPropertiesNFields;
private Component[] componentArray;
private int propertyCount;
private List<PropertyInfo> validProperties;
private List<FieldInfo> validFields;
PropertyInfo[] propertyInfoArray;
private void Awake()
{
if (runAtAwake)
{
GetPropsNFields();
}
}
private void Start()
{
if (runAtStart)
{
GetPropsNFields();
}
}
/// TODO: This could run more efficiently in a coroutine
private void Update()
{
if (runAtRuntime)
{
if (obj != null)
{
GetPropsNFields();
}
}
else
{
Destroy(this);
}
}
[ExecuteInEditMode]
public void GetPropsNFields()
{
if (obj != null)
{ // If the developer has dragged in an object,
// Get the list of components on the object
componentArray = obj.GetComponents(typeof(Component));
// Get the type of each component
string[] componentTypeStrings = new string[componentArray.Length];
for(int i = 0; i < componentArray.Length; i++)
{
componentTypeStrings[i] = componentArray[i].GetType().ToString();
}
// Display the components to the developer
components = componentTypeStrings;
// Get all public, non-inherited properties and fields from the component that the developer has selected
PropertyInfo[] propertyInfoArray = componentArray[componentSelected].GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
FieldInfo[] fieldInfoArray = componentArray[componentSelected].GetType().GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
// Check each property and field on the component to see if it is "valid".
List<string> validPropertiesNFieldsNameList = new List<string>();
validProperties = new List<PropertyInfo>();
for(int i = 0; i < propertyInfoArray.Length; i++)
{ // Check each property
if (propertyInfoArray[i].PropertyType.Equals(activeValue.GetType()))
{ // If the current property matches the type of activeValue,
// Store the property in the list of valid properties and store its name in a grand list of valid properties AND fields
validProperties.Add(propertyInfoArray[i]);
validPropertiesNFieldsNameList.Add(propertyInfoArray[i].Name + ": <" + propertyInfoArray[i].PropertyType + ">");
}
} // finally, store the number of valid properties so that we can differentiate between properties and fields later on
propertyCount = validPropertiesNFieldsNameList.Count;
validFields = new List<FieldInfo>();
for (int i = 0; i < fieldInfoArray.Length; i++)
{ // Check each field
if (fieldInfoArray[i].FieldType.Equals(activeValue.GetType()))
{ // If the current field matches the type of activeValue,
// Store the field in the list of valid fields and store its name in a grand list of valid properties AND fields
validPropertiesNFieldsNameList.Add(fieldInfoArray[i].Name + ": <" + fieldInfoArray[i - propertyInfoArray.Length].FieldType + ">");
validFields.Add(fieldInfoArray[i]);
}
}
// Expose the valid properties and fields to be displayed to the developer
validPropertiesNFields = validPropertiesNFieldsNameList.ToArray();
// Store the property/field value locally
GetSelectedValue();
}
}
private void GetSelectedValue()
{
if (validPropertiesNFields.Length == 0)
{ // If we have NO properties OR fields, set float to default value
activeValue = 0f;
}
else
{ // If there ARE some valid properties or fields
if(componentSelected > (componentArray.Length - 1))
{ // Prevent out of bounds
componentSelected = 0;
}
if (propertyOrFieldSelected < propertyCount)
{ // If the developer has selected a property,
if (validProperties.Count > 0)
{ // If there are any valid properties,
// Store the value of the selected property
if(propertyOrFieldSelected > validProperties.Count)
{ // Prevent out of bounds
propertyOrFieldSelected = 0;
}
activeValue = (float)validProperties[propertyOrFieldSelected].GetValue(componentArray[componentSelected]);
}
}
else
{ // Otherwise the developer has selected a field,
if (validFields.Count > 0)
{ // So if there are any valid fields,
// Store the value of the selected field
int fieldSelected = propertyOrFieldSelected - propertyCount;
if (fieldSelected > (validFields.Count - 1) || fieldSelected < 0)
{ // Prevent out of bounds
fieldSelected = 0;
}
activeValue = (float)validFields[fieldSelected].GetValue(componentArray[componentSelected]);
}
}
}
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(FloatHolder))]
public class FloatHolderEditor : Editor
{
public override void OnInspectorGUI()
{
// var offsetProperty = serializedObject.FindProperty("Offset");
// EditorGUILayout.PropertyField(offsetProperty);
// serializedObject.ApplyModifiedProperties();
// Get the current script
FloatHolder script = (FloatHolder)target;
// Draw the rest standard variables
DrawDefaultInspector();
// Collect the valid properties and fields
script.GetPropsNFields();
// Draw custom drop-down menus for the selected object's components and the fields/properties of that selected component
if (script.components.Length > 0)
{ // If we have any components,
// Draw the drop-down menu for them
var componentSelectedProperty = serializedObject.FindProperty("componentSelected");
//EditorGUILayout.PropertyField(componentSelectedProperty);
componentSelectedProperty.intValue = EditorGUILayout.Popup("Components", componentSelectedProperty.intValue, script.components);
}
if (script.validPropertiesNFields.Length > 0)
{ // if the selected component has any valid properties/fields,
// Draw the drop-down menu for them
var propertyOrFieldSelectedProperty = serializedObject.FindProperty("propertyOrFieldSelected");
propertyOrFieldSelectedProperty.intValue = EditorGUILayout.Popup("Members", propertyOrFieldSelectedProperty.intValue, script.validPropertiesNFields);
}
serializedObject.ApplyModifiedProperties();
}
}
#endif
Your answer
Follow this Question
Related Questions
One Script Referred by Another Script used by Multiple Objects 0 Answers
Passing multiple parameters into InputField AddListener 1 Answer
event is called twice 1 Answer
Losing object reference 2 Answers