I’ve been using Unity for some years, now, and I know the interface like the back of my hand. I’m familiar with obscure functions and sneaky gotchas and yet… mastery of one basic command still eludes me.
‘Frame Selected’ usually centres the screen on the selected object, then zooms the camera so the object fills approx 1/4 of the available frame, automatically adjusting the scene camera’s clipping planes to best capture the object visually. Which is great!
Except sometimes it centres the view on the object, then zooms out to capture almost the entire scene! This leaves me zooming the camera in, rolling and rolling that mouse wheel until I can actually see what I was trying to frame.
Don’t get me wrong. It’s a small issue in the grand scheme of things, and I can mostly work around it. It’s just… a bit frustrating, a bit of a nag, especially if you keep encountering the problem. Given the galloping improvements Unity is always making, it’s annoying this still pops up.
I’m confident that users will know what I’m talking about, but I can provide pictures later if neccessary.
Am I using the ‘Frame Selected’ command correctly? Does anybody out there have a fast workaround (other than laboriously zooming in) for when it seems to misbehave? Does anybody have a straightforward fix for the problem?
I am not sure of all what it uses to frame the selection. But we use some curved shaders in our game. In order to make things render correctly we have to adjust the bounds of the meshes so the camera thinks they are still in view and does not cull them. Unity at least looks at the mesh bounds because now when we frame selected it positions the camera to view the modifed bounds not the actual mesh bounds.
You may have similar issues or have some object that is expanding the bounds unity is calculating to frame in the window.
These kind of issues are just as important because they can make your life harder for no reason and if ignored for a long time, could add up to many development hours from working around it.
I figured out that the view will zoom out very far on an object if it has a child object that is bigger than the root object. For instance I had a character with a large trigger box around it. Whenever I selected the character the view will zoom out to fit the trigger box.
However there is a few rules that it follows when it decides whether to zoom out or not.
-If the root object has a mesh filter with a mesh renderer, it will zoom to fit the root object
-if the root object has a collider, it will zoom to fit the root object
-if the root object is an empty gameobject, it will zoom to fit the largest child object
You can still have a large trigger around your character and have the view zoom in on the root object if you add a collider or mesh renderer + mesh filter to it. It doesn’t even need to be enabled.
This is an issue I would also like to see resolved. After spending a little time on it, I don’t know how much I entirely agree with roger0. That is, the parent relationship of the object you are framing does not seem to affect the level of zoom. At least in Unity 5.6 it doesn’t.
I came up with a workaround that is not ideal, but does offer very good flexibility.
Create a sphere.
Disable the Mesh Renderer and Sphere Collider components.
Now depending on where you position the sphere, and how small or large you scale it, you have complete control over where the view zooms to and how far it zooms in or out when it’s the selected object that you frame.
That’s it. Kind of a waste of time but might be useful.
The child objects only effect the zoom level if the root object is an empty gameobject and the child objects have a mesh renderer + mesh filter and/or collider attached to them. If the root object has any collider or mesh attached it will zoom to fit the root only.
The viewport needs something to zoom in on, so it will prefer to zoom in on colliders and meshes opposed to an empty gameobject. If the root is lacking these, it will zoom in on a child object if they have them.
I had a similar issue with a prefab (F always framed the object and the origin together), and noticed there was a Line Renderer component on one of the child objects which was set to render in World Space. If you have a problematic object like this which does have children in the hierarchy, you can troubleshoot by selecting each child in turn and pressing F to hopefully see which one is causing the issue.
Mind you I have had this problem with some objects which had no children so it’s not a silver bullet.
We generate a bounds object which is then used for the framing.
You can get the bounds in script if you want to visualize it to try and figure out the problems.
UnityEditor.Selection.activeObject = gameObject;
var bounds = UnityEditorInternal.InternalEditorUtility.CalculateSelectionBounds(false, Tools.pivotMode == PivotMode.Pivot);
The way the bounds are calculated in pseudocode:
iterate through all components
if the component is collider encapsulate
if the component is renderer
if active encapsulate
else if MeshFilter then encapsulate mesh bounds
if no colliders, renderers or mesh filters were found:
if the GameObject contains a light then encapsulate the light volume
if the GameObject contains a reflection probe then encapsulate the volume
if the transform is a RectTransform then encapsulate
if nothing was found then encapsulate the transform position
I am not sure if this is addressed as an overide in newer unity versions but you should allow an option for temporarly setting the bounds and frame selection as an overide feature that is based on an object pivot. This is madening when trying to design. I understand the logical reasoning for how your code above calculates the bounds and I understand that alot of people will never need another solution, but for those of us who do its frustrating and seems like a simple integration for a check box that says “from now on when the user selects a game object, it will frame select and zoom in close to the selected object based on its pivot, even if this requires setting a temporary “ghost” collider”. Store these bounds in another area that when checked a zoom can happen on pivot point, or whatever you need to do to get the zoom based on pivot like most people are probably trying to do. A lot of indie developers don’t always set everything up logically or correctly but we want to be able to do basic navigation even if we slopify the objects transform, collider positions ect.
Yeah, this has annoyed me for the last 5 years. The logic needs tweaking, or at least an override.
For example, if your object contains a disabled child with a Point Light w/range of 60, you’ll be zoomed way the hell out every time you frame it. Ideally, Child objects that contain Lights, ParticleSystems, etc. should be ignored by the bounds check, especially if they’re disabled.
I had to write my own frame script as a workaround, but I can’t be the only one frustrated by this. It’s one of those classic usability issues that irritates everyone but not enough to file a bug, so it just aggravates for years.
This is still an issue. And it’s even worse now - sometimes an object will cause it to zoom all the way out, but if I inspect the object causing this (always a light), and then focus the parent again, it fixes itself.
Could you post this script, possibly? I’m guessing they are in no hurry to solve these issues.
It simply focuses the first Renderer it finds. I explored encapsulating child meshes, but find this more “useful” in my projects when the built-in “F” key fails.
Press “ALT-F” to use. (I never use that key for opening the “File” menu.)
If anyone has time to improve it further, please do so!
using UnityEditor;
using UnityEngine;
public class FocusSelected : ScriptableObject {
[MenuItem("Edit/Focus Selected &f")]
static void Focus() {
EditorApplication.ExecuteMenuItem("Window/General/Scene");
var scene_view = SceneView.lastActiveSceneView;
if (scene_view) {
scene_view.orthographic = false;
GameObject selectedObject = Selection.activeObject as GameObject;
Transform camera = scene_view.camera.transform;
camera.rotation = Quaternion.LookRotation(camera.forward);
Bounds bounds = new Bounds(selectedObject.transform.position, Vector3.zero);
Renderer r = selectedObject.GetComponentInChildren<Renderer>();
if (r)
bounds = r.bounds;
const float cameraDistance = 0.5f; // Constant factor
Vector3 objectSizes = bounds.max - bounds.min;
float objectSize = Mathf.Max(objectSizes.x, objectSizes.y, objectSizes.z);
float cameraView = 2.0f * Mathf.Tan(0.5f * Mathf.Deg2Rad * scene_view.camera.fieldOfView); // Visible height 1 meter in front
float distance = cameraDistance * objectSize / cameraView; // Combined wanted distance from the object
distance += 0.5f * objectSize; // Estimated offset from the center to the outside of the object
camera.position = bounds.center - distance * camera.forward;
scene_view.AlignViewToObject(camera);
}
}
}
I made some minor changes, like instead of focusing on the first renderer it finds, it instead loops through all renderers and encapsulates the bounds, but it ignores particle renderers.
I also made it use “f”, not “alt f”, since I wanted to replace the built in one. Later I hope to make it a bit better with the focusing :3
Alright, I made some pretty major changes, sorta replacing the entire thing in my case LOL
Since I made mine replace the built in one, I had to add new logic that doesn’t override pinging the hierarchy and project window.
I also made it focus all selected objects, instead of just the active one, and it turns out there’s a built in function that frames a bounds, so I used that Note I also set the “instant” parameter to false, to make it still focus just like in the old way. As far as I can tell, this is a complete replacement for the built in framer, but if there are an
using UnityEditor;
using UnityEngine;
public class FocusReplacer
{
[MenuItem("Edit/Focus Selected _f")]
static void Focus()
{
if (Selection.activeObject == null)
return;
//Detect object is in project, so just ping
if (AssetDatabase.Contains(Selection.activeObject))
{
EditorGUIUtility.PingObject(Selection.activeInstanceID);
return;
}
//Detect hierarchy window is focused, so just ping
if (EditorWindow.focusedWindow != SceneView.lastActiveSceneView || SceneView.lastActiveSceneView == null)
{
EditorGUIUtility.PingObject(Selection.activeInstanceID);
return;
}
var scene_view = SceneView.lastActiveSceneView;
scene_view.orthographic = false;
Bounds bounds = new Bounds();
bool created = false;
foreach(GameObject obj in Selection.gameObjects)
{
foreach (Renderer r in obj.GetComponentsInChildren<Renderer>())
{
if (r as ParticleSystemRenderer)
continue;
if (!created)
bounds = r.bounds;
else
bounds.Encapsulate(r.bounds);
created = true;
}
}
scene_view.Frame(bounds,false);
}
}
Nice effort! I haven’t been to sleep yet and it’s 10:30AM, so forgive any mistakes, but a couple things I noticed should you (or anyone) want to take this further:
The original “Frame Selected” doesn’t check for EditorWindow.focusedWindow and then Ping. I’d just remove that block, as it’s better to just go ahead and focus if you’ve selected something in the hierarchy and pressed “F”.
Line 36: “as” should be “is”.
If the object or its children don’t contain a renderer, you’re creating an empty bounds and focusing on that. You can add an “if (created)” at line 47 to bypass it, but we almost want to do something there?
The original will focus on a collider’s bounds even if it doesn’t have a renderer. That is good behavior.
Ideally there’d be some sort of “good distance from object” to fall back to when it contains nothing you want to encapsulate. i.e. it would be nice when Focusing a Point Light or empty GameObject if it just put you ~1 meters away and set the camera’s min-clip accordingly. I think if your bounds defaulted to something nice it could work but I’m too tired to focus more myself…
I took some of your advice. The reason I added the focusedWindow logic is because this is replacing my “f key” logic entirely. In other words, it needs to handle both pinging the object in hierarchy and the framing, since those are both features that the f key do.
I also replicated how Unity handles objects with no bounds. If all objects have no bounds, then unity focuses on all of them. Otherwise, it focuses only one the ones that do have bounds. And I also added collision bounds.
It looks like if the object is a light (which counts as a non-bounds) or a non bounds, it focuses a meter or so away. If you want to tweak it, simply tweak the emptyBoundsSize value.
using UnityEditor;
using UnityEngine;
public class FocusReplacer
{
private static Vector3 emptyBoundsSize = Vector3.one;
[MenuItem("Edit/Focus Selected _f")]
static void Focus()
{
if (Selection.activeObject == null)
return;
//Detect object is in project, so just ping
if (AssetDatabase.Contains(Selection.activeObject))
{
EditorGUIUtility.PingObject(Selection.activeInstanceID);
return;
}
//Detect hierarchy window is focused, so just ping
if (EditorWindow.focusedWindow != SceneView.lastActiveSceneView || SceneView.lastActiveSceneView == null)
{
EditorGUIUtility.PingObject(Selection.activeInstanceID);
return;
}
var scene_view = SceneView.lastActiveSceneView;
scene_view.orthographic = false;
Bounds bounds = new Bounds();
bool created = false;
foreach(GameObject obj in Selection.gameObjects)
{
foreach (Renderer r in obj.GetComponentsInChildren<Renderer>())
{
if (r is ParticleSystemRenderer)
continue;
if (!created)
bounds = r.bounds;
else
bounds.Encapsulate(r.bounds);
created = true;
}
foreach (Collider c in obj.GetComponentsInChildren<Collider>())
{
if (!created)
bounds = c.bounds;
else
bounds.Encapsulate(c.bounds);
created = true;
}
}
//Detect no bounds were added
if(!created)
{
foreach (GameObject obj in Selection.gameObjects)
{
if (!created)
bounds = new Bounds(obj.transform.position, emptyBoundsSize);
else
bounds.Encapsulate(new Bounds(obj.transform.position, emptyBoundsSize));
created = true;
}
}
scene_view.Frame(bounds,false);
}
}
Nice work! I’ve had a moment to try some things myself.
It looks like line 17 may serve us better as:
if (EditorWindow.mouseOverWindow != SceneView.lastActiveSceneView || SceneView.lastActiveSceneView == null) {
Note the Editor.mouseOverWindow, which adds some functionality that was missing: if focus is in the hierarchy window, but the mouse is hovering over the scene, pressing “F” should focus the object in the scene.
Also, after re-reading karl_jones’s post above, I noticed that encapsulation of children only happens when Tools.pivotMode == PivotMode.Center. I think that has been a major source of confusion for me as the ignorant user I am, as sometimes focus works nicely while other times it’s zoomed way out for seemingly no reason.
To that end, after playing around with different ideas, I think the built-in Frame works fairly well in pivotMode==PivotMode.Pivot (minus the zooming-out for lights and ParticleSystems), and I’m not a fan of child encapsulation at all. I’ve also found I enjoy having a “maxExtent” value, whereby even large objects have a limit to how far away the camera will be from them. (I also like zooming-in 30% closer than Unity’s default, so I added that as “zoomFactor = 0.7f”.)
Anyway, here’s how it’s looking:
using UnityEditor;
using UnityEngine;
public class FocusReplacer {
static float defaultExtent = 1f; // if no renderer or collider is found, zoom to an object of this size
static float maxExtent = 20f; // controls the maximum "zoom-out" when focusing large objects
static float zoomFactor = 0.7f; // zoom-in more than Unity would by default (1f)
[MenuItem("Edit/Focus Selected &f")]
static void Focus() {
if (Selection.activeObject == null)
return;
//Detect object is in project, so just ping
if (AssetDatabase.Contains(Selection.activeObject)) {
EditorGUIUtility.PingObject(Selection.activeInstanceID);
return;
}
//Detect hierarchy window is focused, so just ping
if (EditorWindow.mouseOverWindow != SceneView.lastActiveSceneView || SceneView.lastActiveSceneView == null) {
EditorGUIUtility.PingObject(Selection.activeInstanceID);
return;
}
Bounds bounds;
Bounds allBounds = new Bounds();
foreach (GameObject obj in Selection.gameObjects) {
Renderer renderer = obj.GetComponentInChildren<Renderer>();
Collider collider = obj.GetComponentInChildren<Collider>();
if (renderer && !(renderer is ParticleSystemRenderer)) {
bounds = renderer.bounds;
} else if (collider) {
bounds = collider.bounds;
} else {
bounds = new Bounds(obj.transform.position, Vector3.one * defaultExtent);
}
if (allBounds.extents == Vector3.zero)
allBounds = bounds;
else
allBounds.Encapsulate(bounds);
}
allBounds.extents *= zoomFactor;
if (allBounds.extents.magnitude > maxExtent)
allBounds.extents = allBounds.extents.normalized * maxExtent;
SceneView.lastActiveSceneView.Frame(allBounds, false);
}
}
“Frame Selected Improvements” are for 2020.1 alpha 19. Several things in there:
Zoom in slightly more than previously,
Properly “frame” a lot more components that have some “volume” (e.g. 2D colliders, LightProbeProxyVolumes, NavMeshObstacles, AudioReverbZones, WindZones, etc. etc.),
More properly handle custom scene view field-of-view setting.