Does anyone know how to get gameobject that was most recently selected in the editor? I’m trying to make a script for parenting your objects to the last thing selected. There seems to be no rhyme or reason to the order of Selection.gameObjects. Selection.activeTransform seems to always be the first object selected. Make Parent in the GameObject menu parents to the first item selection which seems in all cases inferior to parenting to the last object, since you can’t shift select objects in the hierarchy after already selecting your one parent, without it also selecting every object in between. I’m running into this multiple times an hour, and dragging things around in a massive hierarchy is cumbersome.
To Distill my question into a sentence: How to I get the last gameobject selected?
You could do something like this:
using UnityEngine;
using UnityEditor;
[InitializeOnLoad]
public static class YourUtilityClass {
static YourUtilityClass() {
EditorApplication.update += () => {
if (Selection.activeGameObject != null Selection.activeGameObject != LastActiveGameObject)
LastActiveGameObject = Selection.activeGameObject;
};
}
public static GameObject LastActiveGameObject { get; private set; }
}
Then elsewhere:
var lastActiveGO = YourUtilityClass.LastActiveGameObject;
Thanks numberkruncher for the reply, I never knew about InitializeOnLoad, or about EditorApplication.update so I learned a lot from your help. However, LastActiveGameObject seems to always remain the first thing selected. I’m very possibly missing something, but it appears to me that code would only work if activeGameObject was in fact the last object selected, in which case I could just use that.
Okay here’s the solution I came up with based with numberkruncher tip to get me on the scent:
[InitializeOnLoad]
public static class TrackLastSelected {
private static GameObject[] LastSelection;
public static GameObject LastSelected { get; private set; }
static TrackLastSelected() {
EditorApplication.update += () => {
if (Selection.gameObjects != null Selection.gameObjects != LastSelection) {
if (LastSelection != null) {
HashSet<GameObject> LastSelectionHash = new HashSet<GameObject>(LastSelection);
foreach (GameObject go in Selection.gameObjects){
if (LastSelectionHash.Contains(go) == false) {
LastSelected = go;
break;
}
}
}
LastSelection = Selection.gameObjects;
}
};
}
}
It seems to work pretty well. I’m not really sure the performance cost to having to keep track of this stuff every time you select something, but it doesn’t seem to be slow. Funnily enough numberkruncher, you were the one who showed me how use hash sets in another thread a few weeks ago (perhaps you remember), which I think is making this particular code run fast. You’ve helped me in my journey to learn coding immensely!
The approach which I suggested should work!
I would avoid accessing Selection.gameObjects in the update loop because it allocates a lot of garbage every time you access it. Update gets called more than 100 times per second in the editor!!
Also, there is no benefit to using HashSet in this scenario… You might as well just update your array:
LastSelection = Selection.gameObjects;
If you have an editor window there is actually an OnSelectionChange message which you can listen to. That is the most performant solution, but it does require that your editor window be open.
Hmm, well for me, its parenting to the first object selected rather than the last. I don’t see how it could work since Selection.activeGameObject always is the first object. The second object you select would not cause the if to execute since activeGameObject is still the same. And darn, I thought the contains() is faster from a hashset. The selection.gameObjects shouldn’t loop until the selection changes, so it wouldn’t actually be firing that loop 100x a second, only access it once in the if 100x a second which isn’t good either probably, lol. I’m going to try to implement that OnSelectionChange though!
Or maybe it fires again but for whatever reason its still just setting the variable to the first object. Oops and I guess I’m not using an editor window.
HashSet is good when you need high performance lookups/searches, but in this scenario I do not see why you would need that.
I have just verified my original code and it does work how I expected. So I am guessing that you are not after the “Last Active” game object, but rather the “Previously Last Active” game object.
Whether or not you want empty selections to pass through this interface I do not know. So I have assumed that you do. It is super easy to modify this script to ignore empty selections if that would be preferred behaviour. Previously I assumed not, but I think that you are after the “Previously Last Active” game object rather than the last non-null active game object.
Screenshot:
Example Utility Script:
using UnityEngine;
using UnityEditor;
[InitializeOnLoad]
public static class SelectionMonitor {
private static double s_TimeOfLastUpdate;
static SelectionMonitor() {
LastSelection = new GameObject[0];
PreviousSelection = new GameObject[0];
EditorApplication.update += () => {
// Limit updates to every 100ms.
if (EditorApplication.timeSinceStartup - s_TimeOfLastUpdate > 0.1) {
s_TimeOfLastUpdate = EditorApplication.timeSinceStartup;
OnUpdateEvery100ms();
}
};
}
/// <summary>
/// Last known active game object.
/// </summary>
public static GameObject LastActiveGameObject { get; private set; }
/// <summary>
/// Previously known active game object.
/// </summary>
public static GameObject PreviousActiveGameObject { get; private set; }
/// <summary>
/// Last known game object selection.
/// </summary>
public static GameObject[] LastSelection { get; private set; }
/// <summary>
/// Previously known game object selection.
/// </summary>
public static GameObject[] PreviousSelection { get; private set; }
private static void OnUpdateEvery100ms() {
if (LastActiveGameObject != Selection.activeGameObject) {
PreviousActiveGameObject = LastActiveGameObject;
LastActiveGameObject = Selection.activeGameObject;
}
// Let's just do this once, it allocates!!
var selection = Selection.gameObjects;
bool hasSelectionChanged = (selection.Length != LastSelection.Length);
if (!hasSelectionChanged) {
// Selection is the same length as last time, but are the items the same?
for (int i = 0; i < selection.Length; ++i)
if (selection[i] != LastSelection[i]) {
hasSelectionChanged = true;
break;
}
}
if (hasSelectionChanged) {
PreviousSelection = LastSelection;
LastSelection = selection;
}
// Repaint test window every 100ms for testing purposes.
if (TestWindow.Instance != null)
TestWindow.Instance.Repaint();
}
}
Test Window for Viewing Results:
using UnityEngine;
using UnityEditor;
public class TestWindow : EditorWindow {
public static TestWindow Instance { get; private set; }
[MenuItem("Window/Test")]
private static void ShowWindow() {
GetWindow<TestWindow>();
}
private void OnEnable() {
Instance = this;
title = "Test Window";
}
private void OnGUI() {
EditorGUIUtility.labelWidth = 100;
EditorGUILayout.PrefixLabel("Selection:", EditorStyles.boldLabel);
EditorGUILayout.LabelField("Active", Selection.activeGameObject != null ? Selection.activeGameObject.name : "(none)");
EditorGUILayout.LabelField("Length", Selection.gameObjects.Length.ToString());
GUILayout.Space(5);
EditorGUILayout.PrefixLabel("Last:", EditorStyles.boldLabel);
EditorGUILayout.LabelField("Active", SelectionMonitor.LastActiveGameObject != null ? SelectionMonitor.LastActiveGameObject.name : "(none)");
EditorGUILayout.LabelField("Length", SelectionMonitor.LastSelection.Length.ToString());
GUILayout.Space(5);
EditorGUILayout.PrefixLabel("Previous:", EditorStyles.boldLabel);
EditorGUILayout.LabelField("Active", SelectionMonitor.PreviousActiveGameObject != null ? SelectionMonitor.PreviousActiveGameObject.name : "(none)");
EditorGUILayout.LabelField("Length", SelectionMonitor.PreviousSelection.Length.ToString());
GUILayout.Space(5);
}
private void OnSelectionChange() {
Repaint();
}
}
I really appreciate your help here numberkruncher. I think I need to better explain what I want to do. I want to parent my current selection to the latest single thing selected. So for instance: I select “Cheese”, then I ctrl+select “Steak” then I ctrl+select “Ocean”, and press ctrl+shift+alt+p, I want Cheese and Steak to become children of Ocean. Therefore I need something that tracks the most recent single object I’ve added to my selection. Currently, in test window, I’m seeing Selection and Last show Cheese (first thing I select), and continue to show cheese as I select additional items. Then upon deselecting Selection and Last turn to none, and Previous turns to Cheese.
The functionality I need should be:
I select Cheese.
LatestSelected sets to Cheese.
I ctrl+select Steak.
LatestSelect sets to Steak
I ctrl+select Ocean.
LatestSelected sets to Ocean.
I press ctrl+shift+alt+p.
My script parents everything but the latest thing selected, to the latest thing selected.
I don’t think I explained it clearly enough in my original post. I hope that clears things up.
This script allows you to do the following:
- Select Cheese.
- Select Steak.
- Ctrl+Shift+Alt+P.
- Select Ocean.
- Ctrl+Shift+Alt+P.
- Select Tree and then hold Shift to select Boat and then hold control to add Car.
- Ctrl+Shift+Alt+P.
All the above items get parented to Cheese.
using UnityEngine;
using UnityEditor;
[InitializeOnLoad]
public static class ParentMenuItem {
private static double s_TimeOfLastUpdate;
static ParentMenuItem() {
EditorApplication.update += () => {
// Limit updates to every 100ms.
if (EditorApplication.timeSinceStartup - s_TimeOfLastUpdate > 0.1) {
s_TimeOfLastUpdate = EditorApplication.timeSinceStartup;
OnUpdateEvery100ms();
}
};
}
public static GameObject LastActiveGameObject { get; private set; }
public static GameObject PreviousLastActiveGameObject { get; private set; }
private static void OnUpdateEvery100ms() {
// Only update previously active game object when selection contains exactly 1 object.
if (Selection.activeGameObject != null Selection.activeGameObject != LastActiveGameObject Selection.objects.Length == 1) {
PreviousLastActiveGameObject = LastActiveGameObject;
LastActiveGameObject = Selection.activeGameObject;
SceneView.RepaintAll();
}
}
[MenuItem("Tools/Parent to Previously Active %#&p")]
private static void ParentToPreviouslyActive() {
var activeTransform = PreviousLastActiveGameObject.transform;
// Perform re-parenting.
foreach (var go in Selection.gameObjects)
if (go != PreviousLastActiveGameObject)
Undo.SetTransformParent(go.transform, activeTransform, "Parent to Previously Active");
// Allow multiple individual objects to be parented.
Selection.activeGameObject = LastActiveGameObject = PreviousLastActiveGameObject;
}
[MenuItem("Tools/Parent to Previously Active %#&p", true)]
private static bool ValidateParentToPreviouslyActive() {
return PreviousLastActiveGameObject != null Selection.activeGameObject != null;
}
[DrawGizmo(GizmoType.NotSelected)]
private static void DrawGizmoForPreviouslyActiveGameObject(Transform transform, GizmoType type) {
if (PreviousLastActiveGameObject != transform.gameObject)
return;
Color restoreColor = Gizmos.color;
Gizmos.color = Color.red;
Gizmos.DrawWireCube(transform.position, Vector3.one);
Gizmos.color = restoreColor;
}
}
Though, why not just utilize Unity’s active object? it should be set to the first object which becomes active in a multi-object selection. So you could just parent all objects to the active game object right?
I hope that this helps anyhow.
I personally prefer the behaviour of this script.
- Left click to select the object that you want to be the parent.
- Add other objects to the selection.
- Ctrl+Shift+Alt+P.
using UnityEngine;
using UnityEditor;
[InitializeOnLoad]
public static class ParentMenuItem {
private static double s_TimeOfLastUpdate;
static ParentMenuItem() {
EditorApplication.update += () => {
// Limit updates to every 100ms.
if (EditorApplication.timeSinceStartup - s_TimeOfLastUpdate > 0.1) {
s_TimeOfLastUpdate = EditorApplication.timeSinceStartup;
OnUpdateEvery100ms();
}
};
}
public static GameObject LastActiveGameObject { get; private set; }
private static void OnUpdateEvery100ms() {
// Only update previously active game object when selection contains exactly 1 object.
if (Selection.activeGameObject != null Selection.activeGameObject != LastActiveGameObject Selection.objects.Length == 1)
LastActiveGameObject = Selection.activeGameObject;
}
[MenuItem("Tools/Make Parent with Hotkey %#&p")]
private static void MakeParentWithHotkey() {
var gameObjects = Selection.gameObjects;
var activeTransform = LastActiveGameObject.transform;
// Perform re-parenting.
foreach (var go in Selection.gameObjects)
if (go != LastActiveGameObject)
Undo.SetTransformParent(go.transform, activeTransform, "Parent to Previously Active");
}
[MenuItem("Tools/Make Parent with Hotkey %#&p", true)]
private static bool ValidateMakeParentWithHotkey() {
return LastActiveGameObject != null Selection.gameObjects.Length > 1;
}
}
Which seems to behave the same as GameObject | Make Parent.
Simplest implementation is to just execute the default implementation!
using UnityEditor;
static class ParentMenuItem {
[MenuItem("Tools/Make Parent with Hotkey %#&p")]
private static void ParentToPreviouslyActive() {
EditorApplication.ExecuteMenuItem("GameObject/Make Parent");
}
[MenuItem("Tools/Make Parent with Hotkey %#&p", true)]
private static bool ValidateParentToPreviouslyActive() {
return Selection.gameObjects.Length > 1;
}
}
I think your first solution using PreviousLastActiveGameObject could work for me. I could select Ocean, then make an entirely new selection (ocean would get deselected) and parent them all to ocean. Your method is even more powerful since you could continue to make new selections and keep parenting them to ocean. Then when you want a new potential parent, you just single select a new object then deselect it.
As far as the default Make Parent beh, I dislike parenting to the first of a selection because you can’t use shift select without selecting every object in between. So for instance, if ocean is in some random spot of your hierarchy, and you want to shift select a bunch of children who are somewhere, you’re just totally out of luck. However if the parent is the last thing selected, it works fine since your always going to want a single parent so you can use ctrl+click the last thing. Its hard to explain, but basically I’ve never met a scenario where standard Make Parent will help me, I always have a ton of child candidates who will require shift clicking, and attempting to shift click them selects tons of stuff between my parent candidate and my children. Maybe its just the way I work.
I know this post is old but it’s the only one about this subject.
I tried out the codes above but they don’t keep track of the last selected object properly.
My own version is pasted below: the script must be in the editor folder.
Just select objects in the hierarchy and look at the console: it shows the last selected object.
using UnityEngine;
using UnityEditor;
[InitializeOnLoad]
public static class LastSelectedTracker
{
static LastSelectedTracker()
{
EditorApplication.update += EditorUpdate;
}
private static List<GameObject> OrderedSelection = new List<GameObject>();
public static GameObject LastSelected;
private static void EditorUpdate()
{
CreateOrderedSelection();
}
private static void CreateOrderedSelection()
{
bool logLastSelected = false;
var selection = Selection.GetFiltered<GameObject>(SelectionMode.Unfiltered);
for (var i = OrderedSelection.Count - 1; i >= 0; i--)
{
var s = OrderedSelection[i];
if (s == null)
{
OnSelectionModified(null, SelectionChange.Remove);
}
// Check that we contain every transform in the transforms.
if (selection.Contains(s) == false)
{
OnSelectionModified(s, SelectionChange.Remove);
OrderedSelection.Remove(s);
logLastSelected = true;
}
}
foreach (var s in selection)
{
var countDifference = selection.Length - OrderedSelection.Count;
if (countDifference >= 2 && LastSelected != null) // we have made a big difference in count suddenly. We probably used shift to add multiple objects.
{
// ==> we need to try and figure out in which direction (up or down) the shift selection has been executed, and determine the order based on that.
// newObjects contains only the objects that are not ordered yet.
var newObjects = selection.ToList();
for (int i = OrderedSelection.Count - 1; i >= 0; i--)
{
newObjects.Remove(OrderedSelection[i]);
}
// where are the new objects compared to the last selection?
var shiftSelectionGoesUp = ((GameObject)newObjects[0]).transform.GetSiblingIndex() < LastSelected.transform.GetSiblingIndex();
List<UnityEngine.GameObject> orderedNewObjects;
if (shiftSelectionGoesUp)
{
orderedNewObjects = newObjects.OrderByDescending(x => ((GameObject)x).transform.GetSiblingIndex()).ToList();
}
else
{
orderedNewObjects = newObjects.OrderBy(x => ((GameObject)x).transform.GetSiblingIndex()).ToList();
}
foreach (var orderedNewObject in orderedNewObjects)
{
var go = ((GameObject)orderedNewObject);
OnSelectionModified(go, SelectionChange.Add);
OrderedSelection.Add(go);
logLastSelected = true;
}
}
else // this is probably a simple click selection.
{
// Check that we contain every transform in the transforms.
if (OrderedSelection.Contains(s) == false)
{
var go = ((GameObject)s);
OnSelectionModified(go, SelectionChange.Add);
OrderedSelection.Add(go);
logLastSelected = true;
}
}
}
LastSelected = OrderedSelection.Count == 0 ? null : OrderedSelection[OrderedSelection.Count - 1];
if (logLastSelected)
{
if (LastSelected != null)
Debug.Log($"LastSelected: {LastSelected.name}");
else Debug.Log($"LastSelected: none");
}
}
private enum SelectionChange
{
Add,
Remove
}
static void OnSelectionModified(GameObject _go, SelectionChange _change)
{
switch (_change)
{
case SelectionChange.Add:
//Debug.Log($"added: {_go.name}");
break;
case SelectionChange.Remove:
if (_go == null)
{
//Debug.Log($"remove null transform");
}
else
{
//Debug.Log($"remove: {_go.name}");
}
break;
}
}
}