Search Unity

How Do I Use Renderer.isVisible?

Discussion in 'Scripting' started by Studio_Akiba, Jan 3, 2016.

  1. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,422
    I am trying to build the isVisible function into my AI script, but am having a lot of trouble getting it working, and the fact there is no sample code in the Docs doesn't help.

    I am trying to achieve a way so that when the player is not looking at the enemy (which the script is attached to), the enemy can move towards the player, until they turn around.

    Think of a red light, green light system.

    This is a small sample of code where it is used in the Update function, I am getting a red line below the "isVisible" title of the if statement.

    All of the samples I can find show it being used like this without problems, it all compiles in Unity but doesn't work.

    Line 42 and below.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class MainAiScript : MonoBehaviour
    5. {
    6.     private GameObject player;
    7.     public bool hit;
    8.     private Vector3 currentLocation;
    9.     private Vector3 previousLocation;
    10.  
    11.     void Start()
    12.     {
    13.         player = GameObject.FindGameObjectWithTag("Player");
    14.     }
    15.  
    16.     void Update()
    17.     {
    18.         RaycastHit rayHit;
    19.         float distanceToTarget = Vector3.Distance(player.transform.position, transform.position);
    20.         Vector3 direction = (player.transform.position - transform.position).normalized;
    21.         Ray ray = new Ray(transform.position, direction * distanceToTarget);
    22.  
    23.         Color rayColor = Color.red;
    24.  
    25.         if (Physics.Raycast(ray, out rayHit, distanceToTarget))
    26.         {
    27.             if (rayHit.collider.tag == "Player")
    28.             {
    29.                 hit = true;
    30.                 rayColor = Color.green;
    31.                 Debug.DrawRay(transform.position, direction * distanceToTarget, rayColor);
    32.                 currentLocation = player.transform.position;
    33.             }
    34.             else
    35.             {
    36.                 hit = false;
    37.                 rayColor = Color.red;
    38.                 Debug.DrawRay(transform.position, direction * distanceToTarget, rayColor);
    39.                 previousLocation = currentLocation;
    40.             }
    41.         }
    42.         if (!GetComponent<Renderer>().isVisible)
    43.         {
    44.             if (hit == true)
    45.             {
    46.                 transform.position = Vector3.MoveTowards(transform.position, currentLocation, 1 * Time.deltaTime);
    47.             }
    48.             else
    49.             {
    50.                 transform.position = transform.position;
    51.             }
    52.         }
    53.         else
    54.         {
    55.             transform.position = transform.position;
    56.         }
    57.     }
    58. }
     
  2. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,422
    I added some Debug lines to see what was going on, and "Visible" is being called constantly, even when I am not looking at the object in either game or scene view.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class MainAiScript : MonoBehaviour
    5. {
    6.     private GameObject player;
    7.     public bool hit;
    8.     private Vector3 currentLocation;
    9.     private Vector3 previousLocation;
    10.  
    11.     void Start()
    12.     {
    13.         player = GameObject.FindGameObjectWithTag("Player");
    14.     }
    15.  
    16.     void Update()
    17.     {
    18.         RaycastHit rayHit;
    19.         float distanceToTarget = Vector3.Distance(player.transform.position, transform.position);
    20.         Vector3 direction = (player.transform.position - transform.position).normalized;
    21.         Ray ray = new Ray(transform.position, direction * distanceToTarget);
    22.  
    23.         Color rayColor = Color.red;
    24.  
    25.         if (Physics.Raycast(ray, out rayHit, distanceToTarget))
    26.         {
    27.             if (rayHit.collider.tag == "Player")
    28.             {
    29.                 hit = true;
    30.                 rayColor = Color.green;
    31.                 Debug.DrawRay(transform.position, direction * distanceToTarget, rayColor);
    32.                 currentLocation = player.transform.position;
    33.             }
    34.             else
    35.             {
    36.                 hit = false;
    37.                 rayColor = Color.red;
    38.                 Debug.DrawRay(transform.position, direction * distanceToTarget, rayColor);
    39.                 previousLocation = currentLocation;
    40.             }
    41.         }
    42.  
    43.         if (!GetComponent<Renderer>().isVisible)
    44.         {
    45.             Debug.Log("invisible");
    46.  
    47.             if (hit == true)
    48.             {
    49.                 transform.LookAt(player.transform.position);
    50.                 transform.position = Vector3.MoveTowards(transform.position, currentLocation, 1 * Time.deltaTime);
    51.             }
    52.             else
    53.             {
    54.                 transform.position = Vector3.MoveTowards(transform.position, previousLocation, 1 * Time.deltaTime);
    55.             }
    56.         }
    57.         if(GetComponent<Renderer>().isVisible)
    58.         {
    59.             Debug.Log("visible");
    60.             transform.position = transform.position;
    61.         }
    62.     }
    63. }
     
  3. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    1,735
    As I understand it, isVisible will be true if any camera can 'see' it, so in the editor it will return true if the object is visible in the editor scene view, regardless of whether your player camera is looking at it.

    Have you tested the same code in a build yet?
     
    VR_MUSASHI likes this.
  4. MV10

    MV10

    Joined:
    Nov 6, 2015
    Posts:
    1,889
    If WorldToViewportPoint returns 0-1 for x,y and 0 or greater for Z, it's visible to that camera. I wrote this in a static helper class to test visibility to the main camera. I actually use a short timer function that runs for a few seconds to eliminate temporary off-screen situations (my camera continuously moves in a spherical motion):

    Code (csharp):
    1.  
    2.     public static bool IsVisibleToCamera(Transform transform)
    3.     {
    4.         Vector3 visTest = Camera.main.WorldToViewportPoint(transform.position);
    5.         return (visTest.x >= 0 && visTest.y >= 0) && (visTest.x <= 1 && visTest.y <= 1) && visTest.z >= 0;
    6.     }
    7.  
     
  5. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,422
    The scene view camera is not looking at it either, I made sure of that.
     
  6. der_r

    der_r

    Joined:
    Mar 30, 2014
    Posts:
    259
    It never worked for me properly in the editor either. Builds work fine. I just rolled my own in the end.
     
  7. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    1,735
    Odd, I've just set up a test project and put this script onto a cube

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Cube : MonoBehaviour {
    5.  
    6.     // Use this for initialization
    7.     void Start () {
    8.  
    9.     }
    10.  
    11.     // Update is called once per frame
    12.     void Update () {
    13.         Debug.Log("Visible " + GetComponent<Renderer>().isVisible);
    14.     }
    15. }
    If I drag the cube so that it is out of view in both the scene and game view debug.log shows isVisible as false, but if I can see any part of the cube in either view the debug.log shows true. Also rotating the camera to move the cube out of view produces the same result.

    So for me it's working as expected, I can only assume the object you are doing the isVisible test on is visible to a camera.
     
  8. MV10

    MV10

    Joined:
    Nov 6, 2015
    Posts:
    1,889
    callen, Ryiah and Munchy2007 like this.
  9. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    1,735
    Good shout, I'd forgotten about that.
     
  10. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,422
    I have just tested this in a build, and the same thing is happening, it isn't moving at all (which it should be) when not being looked at.
     
  11. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    1,735
    Have you checked that both your conditions are being met, i.e. isVisible == false and hit == true? It could be that although the renderer isn't visible the raycast is missing, and it looks to me like when the raycast misses, your enemy object stays at it's current position.
     
  12. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,422
    I have switched everything over to using OnBecameInvisible and OnBecameVisible, and in the console it is still coming up as the object is visible when it isn't.

    I'm starting to think there may be something wrong with Unity as built-in functions don't seem to be working properly.

    Code (CSharp):
    1. public void OnBecameInvisible()
    2.     {
    3.         Debug.Log("'" + name + "' can *not* be seen anymore.");
    4.     }
    5.  
    6.     public void OnBecameVisible()
    7.     {
    8.         Debug.Log("'" + name + "' can be seen now.");
    9.     }
     
  13. MV10

    MV10

    Joined:
    Nov 6, 2015
    Posts:
    1,889
    Those are triggered on exactly the same conditions that set isVisible.

    There is this Renderer extension method in the wiki, but it essentially does the same thing I wrote earlier:
    http://wiki.unity3d.com/index.php?title=IsVisibleFrom
     
  14. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,422
    The problem this is in the AI script, not attached to the camera so I don't have a way to reference the camera without some really messy "find" functions.
     
  15. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,422
    Update:

    Following this, and getting the camera reference, I was able to implement something along these lines, but it still isn't working.
    Code (CSharp):
    1. if (IsVisibleFrom(GetComponent<Renderer>(), Camera.main))
    2.         {
    3.             if (hit)
    4.             {
    5.                 transform.LookAt(player.transform.position);
    6.                 transform.position = Vector3.MoveTowards(transform.position, currentLocation, 1 * Time.deltaTime);
    7.             }
    8.         }
    9.         else Debug.Log("Not visible");
    10.         {
    11.             if (!hit)
    12.             {
    13.                 transform.position = transform.position;
    14.             }
    15.         }
    16.     }
    17.  
    18.     public static bool IsVisibleFrom(this Renderer renderer, Camera camera)
    19.     {
    20.         Plane[] planes = GeometryUtility.CalculateFrustumPlanes(camera);
    21.         return GeometryUtility.TestPlanesAABB(planes, renderer.bounds);
    22.     }
    Back in Unity I get this error (which is pointing to the "public static bool IsVisibleFrom..." line).

    Assets/Scripts/AI/MainAiScript.cs(62,24): error CS1106: `MainAiScript.IsVisibleFrom(this UnityEngine.Renderer, UnityEngine.Camera)': Extension methods must be defined in a non-generic static class
     
  16. Dantus

    Dantus

    Joined:
    Oct 21, 2009
    Posts:
    5,667
    You have to place IsVsibleFrom in a static class, as explained in the error message. Or use the class as provided in the wiki.
     
  17. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,422
    Got another issue (sorry).

    This method seems to interpret the camera facing the cube as it being able to see it, even if there are models and environmental elements completely blocking view.
     
  18. MV10

    MV10

    Joined:
    Nov 6, 2015
    Posts:
    1,889
    Only a raycast can address blocking by geometry. All these other things mean "visible" in the sense that the camera's rendering pipeline has to consider the object in some way, not visible to the eye in the final fully-rendered scene. And even with raycasting things like shader transparency will not correctly resolve "eyeball visibility"...
     
  19. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,422
    But when I use a complex model in my AI which I will be doing eventually, won't I have to send out a ridiculous amount of rays just to make sure I have all of it scanned?
     
  20. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,422
    Is there any way to get the screen-space pixel information of the object and see if it is visible using that?
     
  21. MV10

    MV10

    Joined:
    Nov 6, 2015
    Posts:
    1,889
    From what I've read, raycasts are extremely fast and cheap, even on mobile, especially if your geometry is relatively simple. You could use a subdivision approach, and with a half-decent frame rate you can even spread the tests over multiple frames. I'd probably do one of the frustum checks above, first, just to see if they're *potentially* visible, then run raycasts. Maybe drop some GameObjects on key points of the enemy geometry and test against those.

    There doesn't seem to be a good solution for shader transparency, except to do additional raycasts from "the other side" of the transparent area. Not sure whether that applies here.

    As for using the rendered output, I wondered whether anyone had tried visibility testing via shader parallelization, and it turns out somebody suggested this two years ago -- and the recommendation (from a Unity dev) was still to use raycasts:

    http://forum.unity3d.com/threads/ai-sight-go-away-from-raycasting-line-of-sight.222531/
     
  22. elpuerco63

    elpuerco63

    Joined:
    Jun 26, 2014
    Posts:
    271
    I am trying to understand how isVisible works as I can't get it to work. Imagine a cube with a pin on the rear face that is not visible at all in the scene because you are looking at the front.

    If I tap the front of the cube and the touch is over where the pin at the rear is the touch passes through the cube and triggers the pins collider trigger even if I test for Renderer.isVisible from which I understand should be false as you cannot see the pin?

    This is a simplistic example as in reality I am using a complex model so a mesh collider is not appropriate
     
  23. MV10

    MV10

    Joined:
    Nov 6, 2015
    Posts:
    1,889
    Explained two posts earlier:

     
    elpuerco63 likes this.
  24. elpuerco63

    elpuerco63

    Joined:
    Jun 26, 2014
    Posts:
    271
    my bad, got ya...mmm not going to be a straight forward resolution :-(
     
  25. MV10

    MV10

    Joined:
    Nov 6, 2015
    Posts:
    1,889
    Well, raycast is the solution to the problem you described. Does your object really need a complex collider? I often use a few simple simple box colliders or simplified mesh colliders (not the visible model mesh, but things like pyramid shapes, for example) for raycast testing when "close" is good enough. You mention touch input and that's rarely a precision operation anyway.
     
  26. elpuerco63

    elpuerco63

    Joined:
    Jun 26, 2014
    Posts:
    271
    I had experimented with box colliders and the like but got mixed results.

    Image a gorilla model that has pins on the front and the back. When looking at the front you cannot see the pins on its back, but if I tap / click on the front that has a pin on its back behind that touch then yes the touch goes through the gorilla and interacts with the pin on its back.

    I have tried putting a box collider inside the gorilla and in part it works, but if I rotate the gorilla do the tap/click ray passes through its arm to the where the pin is again it fires.

    I would think if I ended up modelling the entire gorilla in hidden sphere or box colliders not only would it be messy would it not be expensive?

    This is a totally unknown situation to me as I naively thought if you could not see the pin then neither could the rays...but you educated me today ...
     
  27. elpuerco63

    elpuerco63

    Joined:
    Jun 26, 2014
    Posts:
    271
    I have now littered my model with a collection of capsule and sphere colliders and for the most part it does the trick and cannot really see any real performance impact. Does look very untidy in the editor though!
     
  28. MV10

    MV10

    Joined:
    Nov 6, 2015
    Posts:
    1,889
    elpuerco63 likes this.
  29. elpuerco63

    elpuerco63

    Joined:
    Jun 26, 2014
    Posts:
    271
  30. elpuerco63

    elpuerco63

    Joined:
    Jun 26, 2014
    Posts:
    271
    Don't think in my case this is the answer as this is a simple front/back scenario whereas for me the model can be rotated and so a ray could pass through the model in any direction and angle. So for my case I think the colliders are the answer.
     
  31. OhGandalf

    OhGandalf

    Joined:
    Apr 6, 2017
    Posts:
    1
    Oh man, Thank you, that the answer I have been looking for whyle hours!!!
     
    ScottSlash likes this.