How can I know if a gameObject is 'seen' by a particular camera?
There's a whole slew of ways to achieve this, depending on the precise functionality you need.
You can get events when an object is visible within a certain camera, and when it enters or leaves, using these functions:
OnWillRenderObject, Renderer.isVisible, Renderer.OnBecameVisible, and OnBecameInvisible
Or you can calculate whether an object's bounding box falls within the camera's view frustum, using these two functions:
GeometryUtility.CalculateFrustumPlanes GeometryUtility.TestPlanesAABB
If you've already determined that the object is within the camera's view frustum, you could cast a ray from the camera to the object in question, to see whether there are any objects blocking its view. Physics.Raycast
The answer given by Duck is the most accurate. If you can do with something less accurate, you can consider just checking if the position of the object is seen by the camera.
To find out if a point in the scene is seen by a camera, you can use Camera.WorldToViewportPoint to get that point in viewport space.
If both the x and y coordinate of the returned point is between 0 and 1 (and the z coordinate is positive), then the point is seen by the camera.
I spent a fair while looking into this, the suggestions from @duck where led me down the right path, but I ended up doing it in 3 steps. Note: I am checking objects with several LODs associated with them, so I’m using GetComponentInChildren(), as uposed to GetComponent()
-
Check if the object is in front of the screen: Convert the center point of the object (toCheck) from world coordinates, to screen relitave coordinates. In the screen relitave coordinate system the z axis represents distance infront (+) or behind (-) the screen
-
Check if the objcect is within the camera’s field of view: I found a few ways to do this, but in the end I figured if i already have the point relitave to screen coordinates. If the x,y position in screen coordinates is outside the window size then it is obversly not in the camera’s field of view.
3)Check if the object is being occluded by anything: This was a bit tricky and in the end I used Physics.Linecast(…) instead of Physics.Raycast(…) because I already had the origin and target of the line so I did not need to figure out angles if I used Linecast(…). I just cast the line and if the object it hit did not have the same name as the object I was aiming for (in my case a tree) then I knew something was in the way.
So all together it looks like this:
private bool IsInView(GameObject origin, GameObject toCheck)
{
Vector3 pointOnScreen = cam.WorldToScreenPoint(toCheck.GetComponentInChildren<Renderer>().bounds.center);
//Is in front
if (pointOnScreen.z < 0)
{
Debug.Log("Behind: " + toCheck.name);
return false;
}
//Is in FOV
if ((pointOnScreen.x < 0) || (pointOnScreen.x > Screen.width) ||
(pointOnScreen.y < 0) || (pointOnScreen.y > Screen.height))
{
Debug.Log("OutOfBounds: " + toCheck.name);
return false;
}
RaycastHit hit;
Vector3 heading = toCheck.transform.position - origin.transform.position;
Vector3 direction = heading.normalized;// / heading.magnitude;
if (Physics.Linecast(cam.transform.position, toCheck.GetComponentInChildren<Renderer>().bounds.center, out hit))
{
if (hit.transform.name != toCheck.name)
{
/* -->
Debug.DrawLine(cam.transform.position, toCheck.GetComponentInChildren<Renderer>().bounds.center, Color.red);
Debug.LogError(toCheck.name + " occluded by " + hit.transform.name);
*/
Debug.Log(toCheck.name + " occluded by " + hit.transform.name);
return false;
}
}
return true;
}
private bool I_Can_See(GameObject Object)
{
Plane[] planes = GeometryUtility.CalculateFrustumPlanes(Camera);
if (GeometryUtility.TestPlanesAABB(planes , Object.collider.bounds))
return true;
else
return false;
}
Coming to the party late, but here’s an alternative.
bool IsTargetVisible(Camera c,GameObject go)
{
var planes = GeometryUtility.CalculateFrustumPlanes(c);
var point = go.transform.position;
foreach (var plane in planes)
{
if (plane.GetDistanceToPoint(point) < 0)
return false;
}
return true;
}
GeometryUtility .TestPlanesAABB can throw exceptions, this is a more generic solution, which works for only the point case, solving for ‘bounds’ is left as an exercise for the reader
If the object has a renderer attached, then this should work:
Create a new class cameraSight and array of this class named seenByCameraList
class cameraSight {
var cam : Camera;
var sawMe : boolean;
var seesMe : boolean;
};
var seenByCameraList: cameraSight[];
then initialize it in Start()
var tempCameraList:Camera[] = Camera.allCameras;
seenByCameraList = new cameraSight[tempCameraList.Length];
for (var i:int=0;i<seenByCameraList.Length;i++)
{
seenByCameraList*=new cameraSight();*
seenByCameraList_.cam=tempCameraList*;_
_seenByCameraList.sawMe=false;
seenByCameraList.seesMe=false;
}
Assuming that camera count will not be changed dynamicaly,
in Update() just clear flags to false;
for (i=0;i<seenByCameraList.Length;i++)
{
seenByCameraList.sawMe=seenByCameraList.seesMe;_
_seenByCameraList.seesMe=false;
}
If Unity whave to render this object, it will call OnWillRenderObject() , where we modify the flags for cameras, to which this object is visible
function OnWillRenderObject () {
for (var i:int=0;i<seenByCameraList.Length;i++)
{
if (seenByCameraList.cam==Camera.current) seenByCameraList.seesMe=true;
}
}
And the final function which you call for testing visibility to particular camera
function isRendererVisibleByCamera(observerCamera:Camera){
for (var i:int=0;i<seenByCameraList.Length;i++)
{
if (seenByCameraList.camera==observerCamera) return seenByCameraList.sawMe;
}
}*
To avoid clearing of flag, there is sawMe flag which copies a value of seesMe from previous frame.
So information is one frame delayed._
C#:
if(renderer.isVisible) {
print ("Found!");
}
else {
print ("Lost!");
}
Javascript:
function OnBecomeVisible () {
print("Found!");
}
function OnBecomeInvisible() {
print("Lost!");
}
renderer.isVisible does not exist anymore. You have to use it like gameObject.GetComponent<Renderer>().isVisible
now
CAUTION: If you are using any of the following method: OnWillRenderObject, Renderer.isVisible, Renderer.OnBecameVisible, and OnBecameInvisible
Please note that the gameobject renderer.isVisible would always be true if you have enabled shadows in the Main Scene Directional Light. You would also need to close the scene view tab or tilt the scene view camera somewhere else during playtesting.
I am currently using Renderer.OnBecameVisible, and OnBecameInvisible to trigger checks and then use Camera frustrum check if both true then gameobject is inside the view
the code will run even if the target is blocked by an object, any solution to this?,the code will run even if the target is blocked by an object
inspired by the answer from @oPless_ , instead of checking one point, checking all the vertices of the bounding box works for me
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public bool CheckIfObjectWithinView(GameObject go, Camera cam) {
if (go == null) return false;
var bounds = new Bounds();
go.GetComponentsInChildren < Renderer > ().ForEach(r => bounds.Encapsulate(r.bounds));
var planes = GeometryUtility.CalculateFrustumPlanes(cam);
var points = GetEightBoundsVertices(bounds);
if (points.Count(p => TestPoint(planes, p)) == points.Count) {
//the whole object is within cam view
return true;
}
return false;
//check if all vertices points are in the positive direction of all cam frustum planes
//if it is true, the gameobject is within camview
bool TestPoint(Plane[] planes, Vector3 point) => !planes.Any(plane => plane.GetDistanceToPoint(point) < 0);
List < Vector3 > GetEightBoundsVertices(Bounds bounds) => new List < Vector3 > () {
bounds.min,
bounds.max,
new Vector3(bounds.min.x, bounds.min.y, bounds.max.z),
new Vector3(bounds.min.x, bounds.max.y, bounds.min.z),
new Vector3(bounds.max.x, bounds.min.y, bounds.min.z),
new Vector3(bounds.min.x, bounds.max.y, bounds.max.z),
new Vector3(bounds.max.x, bounds.min.y, bounds.max.z),
new Vector3(bounds.max.x, bounds.max.y, bounds.min.z)
};
}
you shoud use :
GetComponent() .isVisible ();
becouse :
renderer.isVisible()
is no more working