- Home /
Problem in assigning component to variable
There are two scripts A and B which extend C class and C extends MonoBehaviour and there is a G1 gameobject that has A and B scripts. And there is D script that has public C cVariable and is attached to G2 gameobject. In the inspector of the G2 I want to assign B component of G1 to cVariable of D script. But the A script is placed higher than B script and also can be assigned to cVariable. When I click on the little circle of the cVariable object field or drag my G1 gameobject to this field unity brings only A component. So how can I choose between A and B without moving up B script in the G1 inspector?
Answer by Bunny83 · Nov 11, 2017 at 06:49 PM
Of course you can do this in the editor. To me the question was quite clear. ^^
The trick is to use two inspector windows and lock one of them so you can actually inspect the two gameobjects at once. Now you can simply drag the component instance directly. I've made an animated GIF some time ago that should be more clear:
So you simply grab the components "header" and drag it to your variable. Ideally it would be nice if Unity would show some sort of popup / dropdown when you drop a gameobject that has multiple possible targets so you can choose one.
Of course it gets easier when the target script is on the same gameobject as you can directly drag the components. However if they are on different gameobjects you have to use two inspectors. In my gif example the variable type is actually "UnityEngine.Object" so we could assign any component, Mesh, Material or GameObject. So when just dragging a gameobject from the hierarchy it would assign the gameobject itself.
edit
Ok, big update
Since i said above that it would be awesome if Unity showed some sort of dropdown to select the object you want, i quickly implemented a PropertyDrawer for ObjectFields which does exactly that.
Note: When you use the attribute "ObjectDropdownAttribute" on a serialized reference type field (only types derived from UnityEngine.Object of course) you can now hold "SHIFT" while dropping an object on the field. This will trigger my custom dropdown logic. It will filter out all components / objects which are assignable to the field type.
Instead of the "ObjectDropdown" attribute you can also use the "ObjectDropdownFilter" attribute which allows you to specify an additional filter type on the field. This is useful for limiting components which should implement a certain interface.
First of all here's the PropertyDrawer (dropbox download). This script need to be placed in an editor folder:
//ObjectSelectorDropdown.cs
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEditor;
namespace B83.PropertyDrawers
{
[CustomPropertyDrawer(typeof(ObjectDropdownAttribute))]
public class ObjectSelectorDropdown : PropertyDrawer
{
List<Object> m_List = new List<Object>();
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
Event e = Event.current;
if (property.propertyType == SerializedPropertyType.ObjectReference)
{
if ((e.type == EventType.DragPerform ||
e.type == EventType.DragExited ||
e.type == EventType.DragUpdated ||
e.type == EventType.Repaint) &&
position.Contains(e.mousePosition) && e.shift)
{
if (DragAndDrop.objectReferences != null)
{
m_List.Clear();
foreach (var o in DragAndDrop.objectReferences)
{
m_List.Add(o);
var go = o as GameObject;
if (go == null && o is Component)
{
go = ((Component)o).gameObject;
m_List.Add(go);
}
if (go != null)
foreach (var c in go.GetComponents<Component>())
if (c != o)
m_List.Add(c);
}
var fieldInfo = property.GetPropertyReferenceType();
if (fieldInfo != null)
{
var type = fieldInfo.FieldType;
for (int i = m_List.Count - 1; i >= 0; i--)
{
if (m_List[i] == null || !type.IsAssignableFrom(m_List[i].GetType()))
m_List.RemoveAt(i);
}
}
var att = attribute as ObjectDropdownFilterAttribute;
if (att != null)
{
var type = att.filterType;
for (int i = m_List.Count - 1; i >= 0; i--)
{
if (!type.IsAssignableFrom(m_List[i].GetType()))
m_List.RemoveAt(i);
}
}
if (m_List.Count == 0)
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
else
{
DragAndDrop.visualMode = DragAndDropVisualMode.Link;
if (e.type == EventType.DragPerform)
{
GenericMenu gm = new GenericMenu();
GenericMenu.MenuFunction2 func = (o) => {
property.objectReferenceValue = (Object)o;
property.serializedObject.ApplyModifiedProperties();
};
foreach (var item in m_List)
gm.AddItem(new GUIContent(item.name+"("+item.GetType().Name+")"), false, func, item);
gm.ShowAsContext();
e.Use();
}
}
m_List.Clear();
}
}
EditorGUI.ObjectField(position, property, label);
}
else
EditorGUI.PropertyField(position, property, label);
}
}
public static class SerializedPropertyExt
{
public static FieldInfo GetPropertyReferenceType(this SerializedProperty aProperty)
{
var currentType = aProperty.serializedObject.targetObject.GetType();
FieldInfo fi = null;
var parts = aProperty.propertyPath.Split('.');
foreach (string fieldName in parts)
{
fi = currentType.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (fi == null)
return null;
currentType = fi.FieldType;
}
return fi;
}
}
}
In addition you need this script as well which defines the attributes (dropbox download). This script must not be in an editor folder but simply part of your normal runtime scripts
// ObjectDropdownAttribute.cs
using UnityEngine;
public class ObjectDropdownAttribute : PropertyAttribute
{
}
public class ObjectDropdownFilterAttribute : PropertyAttribute
{
public System.Type filterType;
public ObjectDropdownFilterAttribute(System.Type aType)
{
filterType = aType;
}
}
Usage
public class SomeScript : MonoBehaviour
{
[ObjectDropdown]
public SomeBaseClass objRef;
}
If you attach the ObjectDropdown attribute like this you can still use the object field as usual. However if you hold down SHIFT before you drop an object on the field you will actually get a dropdown menu which lets you select one of the compatible objects / components available.
Answer by hrgchris · Nov 11, 2017 at 05:01 PM
Hi
Wow - with respect, that was the most confusingly asked question I've ever heard :) For future reference, feel free to use actual names for examples, and a screen shot speaks a 1000 words!
Anyway, I think what you're asking is:
you have a base class, Animal, derived from Monobehaviour
you have 2 derived classes, Dog and Cat
you have a single game object (animals) that has both Dog and Cat components attached
you have another component on another game object (animalref) that has a field of type Animal
how can you make sure that your Animal field on animalref points to the Dog, not the Cat or visa versa
Unfortunately, the answer is I believe that you can't in the UI. In thoery if you knew exactly what you wanted you could add an 'OnValidate' function to your 'animalref' that looked up the correct thing in code. That said, I would recommend just having a bit of a rethink about how you structure your scene in this case - best to work with unity, not against it!
-Chris
Sorry for such dumbly-written question, I just don't know English very well! $$anonymous$$y problem is that Dog script is first script and Cat is second script attached to gameobject (animals). I want to attach Cat to Animal, but I can't cause unity takes only first script of type Animal which is Dog and doesn't let me to choose between Dog and Cat.
Hi $$anonymous$$. No problem on the question - that's why I mentioned a screen shot, as pictures can be much better at communicating a problem, especially when language is an issue.
I understand the issue, and as I say, I don't think it is something you can do in the Unity UI. You may be able to write some code that specifically links your objects together, but my advice is to take a step back and have a rethink first. There is probably a way of achieving your goal without having these 2 scripts on the same game object, which is how Unity 'wants' you to work.
Ok in my case I have a Tank and CameraControl scripts (simple $$anonymous$$onoBehaviour scripts derived from $$anonymous$$onoBehaviour) which are attached to Tank gameobject. I have a Game script attached to Game gameobject. It is for pausing game and when game is being paused Game script should disable some Behaviours including Tank and CameraControl and for this I added List of Behaviours which is public and shown in the Inspector of the Game gameobject. I assign Tank script to Element 0 by draging tank gameobject to Element 0. I also want to assign CameraControl script to Element 2 but unity assigns Tank script again, cause Tank is also Behaviour and it is the first script of Tank gameobject. Thank you for helping me Chris!