- Home /
How does Unity handle GameObject selection in the Scene View?
Good afternoon all,
I am working on some custom editors that work in the scene view, and have run into a problem. My editor can be activated while an object is selected by right clicking, which then displays a properties window in the Scene view. In order to prevent the object from being deselected when the user is moving around the scene, I set the default control to passive.
HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
This works great until it comes time to actually do stuff in edit mode. What I'm trying to do is make it so that whenever the user is in edit mode and clicks on an object in the scene, that objected is added to a list of Game Objects.
I know that Raycast is the preferred way of doing this, the problem is that most of the objects in my scene don't have colliders. I'm working with architectural visualizations, and am bringing in models with literally hundreds of thousands of objects in them; adding colliders to everything would take hours, and would have to happen every time I update and reload the model.
Since Raycasting is pretty much out of the question, I'm trying to come up with something else. The thing is, I know this is possible. Unity does it automatically every time I'm in the scene view. I can easily select objects without colliders, which means that there must be a built in way of managing this. My question is, has anyone ever found out how Unity handles object selection in the scene view, and is this system accessible through the UnityEditor or UnityEngine dll's? Short of that, does anyone have any thoughts on a way this could be accomplished?
Thanks for any help you can give me.
Well, you could get the AABBs that are automatically generated for every mesh and loop through with a custom raycast call to choose the best, but that would be imprecise and the loop could be long and slow.
Alternately, you could import mesh colliders for them all, and use those for raycasting, which should be very fast, but if the objects are numerous or dense, it could slow down the scene.
There are far too many objects in these scenes to add colliders to them, even doing it automatically would take hours each time I update the model. (These models are being brought in from Revit). The boundaries idea would also take way too long unfortunately. I saw an example using quad trees and registering everything beforehand, but these models update so quickly that this would also be a major time constraint.
Thank you for the ideas though, I appreciate the input.
Hmmmm.... then I can't imagine a way to do it. Unity was designed for real-time applications, and CAD tends to be too much at once for real-time for reasons such as this.
The only other thing I can think of would be to pre-render the objects with unique colors to a rendertarget and get the correct object from a color-lookup after getting the pixel the mouse clicked on. It is slow to pull a frame buffer from the GPU, but if you only do it when the mouse is clicked, then you'd be bound by one operation, not the size of your scene data.
I don't follow your suggestion about pre-rendering objects. It sounds that that would take an extra step through 3ds max to find everything that will be added to the list. Unfortunately, that's something I can't really know beforehand, and would probably be as time consu$$anonymous$$g as finding the objects I want to add to the list and adding colliders to them manually.
I find it really surprising that there's not a way to handle this already. Unity already knows how to do it, so I don't get why they don't expose that function to the user.
Well, Unity isn't perfect about it either. I sometimes have trouble selecting the right object in a big scene.
So, the idea I mentioned before is basically to do your selection in screen space rather than in world space. Unity probably does something like this (only much more elegantly) under the hood.
Give all imported objects a unique color. You should be able to write a script to do so on instantiation pretty easily.
Render all objects to a rendertarget texture with a flat shader that outputs only the object's color, no lighting or anything. This should be a completely separate pass from the actual render that appears on screen.
When a mouse-click is detected, get the render target, and look up the color of the pixel the user clicked on. Use a Dictionary or something to then get the correct object that was assigned that color.
Answer by Eno-Khaon · May 12, 2015 at 09:30 PM
I haven't had a chance to test this out, but here's some food for thought. From what I found at http://answers.unity3d.com/questions/600358/how-to-select-a-game-object-by-script-in-editor-mo.html it should be possible to have a last-known GameObject selection to compare against if you're aiming to only add to a list (and then make sure to be able to clear out the list when needed).
// C#
using System.Collections.Generic
// ...
GameObject lastSelection;
[SerializeField]
List<GameObject> objectList = new List<GameObject>();
// ...
if(Selection.activeGameObject != lastSelection)
{
lastSelection = Selection.activeGameObject;
objectList.Add(lastSelection);
}
// ...
Additionally, according to Unity's entry on activeGameObject, it wouldn't hurt to keep tabs on what you're adding to the selection by serializing the list, just to be sure you're not getting any weird, erroneous results. Plus, any necessary sanity checks for ensuring you're not putting the same object in the list over and over... unless you want to do that, obviously.
I came across that thread as well, and was looking at it as an option. The problem is that my edit mode structure doesn't allow for this to work. The way it's set up is this:
User Selects Game Object in the scene
User right clicks in scene view
-Start drawing the GUI for the menu and edit mode systems-
Scene view is set to passive mode to prevent deselection of the active Game Object.
The properties menu is shown, which has a button to activate the edit mode.
In edit mode, I need to turn off passive mode so the user can select objects to add to the list, but then turn passive mode back on for any other menu interactions.
Once the user hits a button to exit the edit menu system, the screen view is set back to native so that normal selection can occur.
Unfortunately, it doesn't appear that I can toggle passive mode on and off like that, it's either all or nothing.
I guess what I could do is not set the Scene View to passive mode, but just intercept and prevent any object selections if I'm not in edit mode. Thank you for the input.
Ohhhh, that. Here's the gist of a script I made a while back to change rules depending on what you are or aren't doing:
bool clickToggle;
void OnSceneGUI()
{
Event e = Event.current;
int controlID = GUIUtility.GetControlID(GetHashCode(), FocusType.Passive); // Save editor click data first thing
if(e.type == EventType.$$anonymous$$eyDown && e.keyCode == $$anonymous$$eyCode.C) // Whichever keyboard key, but Z and X change settings when pressed/held, for instance
{
clickToggle = true;
}
else if(e.type == EventType.keyUp && e.keyCode == $$anonymous$$eyCode.C)
{
clickToggle = false;
}
if(clickToggle && e.type == EventType.$$anonymous$$ouseDown && e.button == 0)
{
// Do something while button is held
}
else if(clickToggle && e.type == EventType.Layout)
{
HandleUtility.AddDefaultControl(controlID); // $$anonymous$$eep selection
}
}
Namely, this was set up so that holding a key ("C" in this case) would prevent deselection and allow secondary functionality when I clicked on the same object I had selected. A few tweaks, such as inverting the state of the clickToggle variable, and the default will become NOT changing selection unless a key is held.
Hmm, this looks interesting. I'm not clear on the purpose of the control id though. Let me see if I follow what happens:
You get the current event.
You get the control id for the passive focus type. (Could you explain what this is a bit more? I'm unfamiliar with it.)
If the user presses down c, you activate the click toggle
I the user lets up on c, you deactivate the click toggle
If clickToggle is active and the user presses down, do whatever you want it to.
If clickToggle is not active, temporarily set the default control to passive to prevent the user from selecting a different object. This will reset on the next GUI cycle so the screen can be interacted with again.
Am I following that correctly? If so, I might be able to make this work for me. It'd still be great if I could get access to Unity's object selection system, but barring that, this should probably get me what I need. Thank you!
Thank you for the help, I've used your initial post, which tracks the active game object, and made it work for what I'm needing.
I'll mark it as the correct answer, but I'd still like to see if there's a way to access the built in object selection system Unity uses for its scene view.
Thanks!