OnBecameInvisible in the Editor?

Does anyone happen to know of a way to deal with on became invisible in the editor? It’s a massive pain to relocate my scene camera anytime I want to play the game to make sure I get the correct camera calls. Is there a generally accepted way to handle this particular problem? Is there another “OnWontRender” callback that is called anytime a camera stops rendering an object and not just when no cameras will render the object?

No, there is no real replacement for that callback that does not take the sceneview camera into account. The sceneview camera is just a normal camera, just that it is hidden in the hierarchy.

Depending on what you want to achieve you may be able to use [OnWillRenderObject][1] which is called for every camera that is about to render the object. Of course you don’t get a call if an object is not rendered by a camera.

However you could manually test if an object is inside the camera frustum. You could use Unity’s [CalculateFrustumPlanes][2] as well as [TestPlanesAABB][3]. However CalculateFrustumPlanes allocates memory for the Plane array.

Instead you could use this extension i wrote:

// VisibilityTools.cs
using UnityEngine;

public enum EFrustumIntersection
{
    Outside,
    Inside,
    Intersecting
}

public struct Frustum
{
    public Vector4 l;
    public Vector4 r;
    public Vector4 b;
    public Vector4 t;
    public Vector4 n;
    public Vector4 f;

    public Plane left { get { return new Plane { normal = l, distance = l.w }; } }
    public Plane right { get { return new Plane { normal = r, distance = r.w }; } }
    public Plane bottom { get { return new Plane { normal = b, distance = b.w }; } }
    public Plane top { get { return new Plane { normal = t, distance = t.w }; } }
    public Plane near { get { return new Plane { normal = n, distance = n.w }; } }
    public Plane far { get { return new Plane { normal = f, distance = f.w }; } }
    public Vector4 this[int aIndex]
    {
        get
        {
            switch(aIndex)
            {
                case 0: return l;
                case 1: return r;
                case 2: return b;
                case 3: return t;
                case 4: return n;
                case 5: return f;
                default: throw new System.ArgumentOutOfRangeException("aIndex", "value must be between 0 and 5");
            }
        }
    }

    public EFrustumIntersection TestBounds(Bounds aBounds)
    {
        var min = aBounds.min;
        var max = aBounds.max;
        var x = new Vector2(min.x, max.x);
        var y = new Vector2(min.y, max.y);
        var z = new Vector2(min.z, max.z);
        var res = EFrustumIntersection.Inside;

        for (int i = 0; i < 6; i++)
        {
            var plane = this*;*

int xi = plane.x > 0 ? 1 : 0;
int yi = plane.y > 0 ? 1 : 0;
int zi = plane.z > 0 ? 1 : 0;
if (plane.x * x[xi] + plane.y * y[yi] + plane.z * z[zi] + plane.w < 0)
return EFrustumIntersection.Outside;
if (plane.x * x[1-xi] + plane.y * y[1-yi] + plane.z * z[1-zi] + plane.w < 0)
res = EFrustumIntersection.Intersecting;
}
return res;
}
public EFrustumIntersection TestPoint(Vector3 aPoint)
{
for (int i = 0; i < 6; i++)
{
var plane = this*;*
float d = plane.x * aPoint.x + plane.y * aPoint.y + plane.z * aPoint.z + plane.w;
if (d < 0)
return EFrustumIntersection.Outside;
}
return EFrustumIntersection.Inside;
}
}

public static class VisibilityTools
{
public static Frustum GetFrustum(this Camera aCam)
{
var m = aCam.projectionMatrix * aCam.worldToCameraMatrix;
Frustum res;
var x = m.GetRow(0);
var y = m.GetRow(1);
var z = m.GetRow(2);
var w = m.GetRow(3);

res.l = GetNormalizedPlane(w + x);
res.r = GetNormalizedPlane(w - x);

res.b = GetNormalizedPlane(w + y);
res.t = GetNormalizedPlane(w - y);

res.n = GetNormalizedPlane(w + z);
res.f = GetNormalizedPlane(w - z);
return res;
}

private static Vector4 GetNormalizedPlane(Vector4 aVec)
{
return aVec*(1f / Mathf.Sqrt(aVec.x * aVec.x + aVec.y * aVec.y + aVec.z * aVec.z));
}
}
It basically provides the same functionality as Unity’s methods but uses a struct with 6 plane definitions instead of an array. It also differentiates between outside, inside and intersecting which might also be useful in some cases. Note that the check is based on the axis aligned bounding box you provide (which you usually get from “Renderer.bounds”). That AABB usually is larger than the actual object. Though Unity uses the same for it’s visibility check.
To use this extension, just place this “VisibilityTools” script into your project. This allows you to test the visibility of an object against a specific camera. If you need to test multiple objects, make sure you “cache” and reuse the frustum definition you get from GetFrustum. Though keep in mind whenever the camera moves, rotates or is changed in any way you need to refresh the frustum. There’s no problem refreshing the frustum once every frame. Though you might want to avoid calculating it several times within a single frame.
Renderer someObject;

Frustum f = Camera.main.GetFrustum();
if (f.TestBounds(someObject.bounds) == EFrustumIntersection.Outside)
{
// “someObject” is not rendered by the main camera
}
As additional note: the normals of the frustum planes point “inwards”. That means a given point is inside the frustum when the point is on the “positive side” of each plane. Likewise if a point is behind any of the 6 planes(on the negative side) the point is outside.
_[1]: https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnWillRenderObject.html*_
_
[2]: https://docs.unity3d.com/ScriptReference/GeometryUtility.CalculateFrustumPlanes.html*_
_*[3]: https://docs.unity3d.com/ScriptReference/GeometryUtility.TestPlanesAABB.html*_

Put this at the beginning of your OnBecameInvisible:

#if UNITY_EDITOR
if(Camera.current && Camera.current.name == "SceneCamera") return;
#endif

The preprocessor directives around the actual line prevent this then-unneccessary test to be compiled into your build.

A more precise solution (not relying on the camera’s name) would be to check if Camera.current is referenced in the array returned by SceneView.GetAllSceneCameras().

Vector3 viewPos = Camera.main.WorldToViewportPoint(this.transform.position);
if (viewPos.x >= 0 && viewPos.x <= 1 && viewPos.y >= 0 && viewPos.y <= 1)
{
hasBeenVisible = true;
}
else
{
if(hasBeenVisible) Destroy(gameObject);
}