Offscreen Target Indicator

Updated on April 4, 2018 in [A] Unity Scripting
Share on Facebook0Tweet about this on TwitterShare on Google+0Share on Reddit0
6 on March 12, 2018

Hello !

I wanted to know that how can I create an Offscreen Target Indicator in Unity 3D ?

All of the videos and forums that I searched , hadn’t made things clear .

Thanks so much .

  • Liked by
  • Mouledoux
Reply
5 on March 14, 2018

Sorry it took so long, but some of this was new grounds for me.

Anyway here is a link to the script on GitHub, I’ll paste it below too.

 

The problem is something is off the screen, and we want to know where it is off the screen. That’s a 3 part problem that Unity has some builtin solutions for.

1st: where is it in the scene? That’s just the GameObject’s transform.position, easy enough.

2nd: where is it on the screen? You could do this manually, and get the relative distance and position from the camera and convert that into a 2D vector with relation to the camera’s view angle, but Unity has a handy function called WorldToViewportPoint, which takes in a Vector3, and gives us a convenient Vector2 between 0,0 (bottom left) to 1,1 (top right)

3rd: where is it on the UI? Now you could actually get this directly from the the world position, but that causes issues when the object is behind you and the numbers are a bit harder to work with. So instead, if we take that Vector2 we just made, and pass it into ViewportToScreenPoint, this will give us, in pixels, a point on the canvas that is the same as the point on the screen.

 

That’s all 3 problems solved, so now lets draw an icon on the screen.

Well, not quite, I lied, it’s actually a 4 part problem, but the 4th can’t all be done with builtin functions. See, so far we’ve only been working in the viewport (whats on the screen right now), but we want to track stuff NOT in the viewport with stuff that IS in the viewport (the icon that will be at the edge). Without going too much into detail, all you really need to do is clamp the values to the icon doesn’t go off the screen, and maybe a few extra things in the event the object is directly behind you.

 

So that’s it. Here’s the script like I said. It’s not perfect and really only has a few functions at the moment, but hopefully this is enough to get you going and adding your own things to it. All you need to do to use this, is to drop the script on whatever object you want to track, give it an icon to display, and make sure you have a canvas in your scene. It will add the icon to the scene at runtime, you do not need to set a reference in the inspector.

using UnityEngine;
using UnityEngine.UI;
public class TargetIndicator : MonoBehaviour
{
    private Camera mainCamera;
    private RectTransform m_icon;
    private Image m_iconImage;
    private Canvas mainCanvas;
    private Vector3 m_cameraOffsetUp;
    private Vector3 m_cameraOffsetRight;
    private Vector3 m_cameraOffsetForward;
    public Sprite m_targetIconOnScreen;
    public Sprite m_targetIconOffScreen;
    [Space]
    [Range(0, 100)]
    public float m_edgeBuffer;
    public Vector3 m_targetIconScale;
    [Space]
    public bool ShowDebugLines;
 
 
    void Start ()
    {
       mainCamera = Camera.main;
       mainCanvas = FindObjectOfType<Canvas>();
       Debug.Assert((mainCanvas != null), "There needs to be a Canvas object in the scene for the OTI to display");
       InstainateTargetIcon();
     }
 
       void Update ()
       {
       if(ShowDebugLines)
         DrawDebugLines();
       UpdateTargetIconPosition();
       }
 
 
      private void InstainateTargetIcon()
       {
          m_icon = new GameObject().AddComponent<RectTransform>();
          m_icon.transform.SetParent(mainCanvas.transform);
          m_icon.localScale = m_targetIconScale;
         m_icon.name = name + ": OTI icon";
         m_iconImage = m_icon.gameObject.AddComponent<Image>();
          m_iconImage.sprite = m_targetIconOnScreen;
       }
 
 
       private void UpdateTargetIconPosition()
       {
          Vector3 newPos = transform.position;
          newPos = mainCamera.WorldToViewportPoint(newPos);
          if(newPos.z < 0)
             {
                newPos.x = 1f - newPos.x;
                newPos.y = 1f - newPos.y;
                newPos.z = 0;
               newPos = Vector3Maxamize(newPos);
             }
        newPos = mainCamera.ViewportToScreenPoint(newPos);
       newPos.x = Mathf.Clamp(newPos.x, m_edgeBuffer, Screen.width - m_edgeBuffer);
        newPos.y = Mathf.Clamp(newPos.y, m_edgeBuffer, Screen.height - m_edgeBuffer);
       m_icon.transform.position = newPos;
       }
 
 
    public void DrawDebugLines()
    {
       Vector3 directionFromCamera = transform.position - mainCamera.transform.position;
      Vector3 cameraForwad = mainCamera.transform.forward;
       Vector3 cameraRight = mainCamera.transform.right;
       Vector3 cameraUp = mainCamera.transform.up;
      cameraForwad *= Vector3.Dot(cameraForwad, directionFromCamera);
       cameraRight *= Vector3.Dot(cameraRight, directionFromCamera);
       cameraUp *= Vector3.Dot(cameraUp, directionFromCamera);
      Debug.DrawRay(mainCamera.transform.position, directionFromCamera, Color.magenta);
      Vector3 forwardPlaneCenter = mainCamera.transform.position + cameraForwad;
      Debug.DrawLine(mainCamera.transform.position, forwardPlaneCenter, Color.blue);
       Debug.DrawLine(forwardPlaneCenter, forwardPlaneCenter + cameraUp, Color.green);
       Debug.DrawLine(forwardPlaneCenter, forwardPlaneCenter + cameraRight, Color.red);
    }
 
 
       public Vector3 Vector3Maxamize(Vector3 vector)
       {
       Vector3 returnVector = vector;
       float max = 0;
       max = vector.x > max ? vector.x : max;
       max = vector.y > max ? vector.y : max;
       max = vector.z > max ? vector.z : max;
       returnVector /= max;
       return returnVector;
       }
}
 
on March 16, 2018

WOW !

I don’t know how to thank you .

I’m going to paste this code into unity in order to see whether it works or not .

 

Thank you so much .

 

on March 16, 2018

Hello again .

I pasted your code . It works perfectly fine but , you haven’t still wrote the part that when the target goes offscreen , the icon of the target would change .

 

Thanks .

on March 16, 2018

Correct. There is a place for the off screen icon, but I never added in any functionality to it. You could add it in pretty easy though.

 

Because WorldToViewportPoint gives us a 2d vector no larger than 1, 1 (at least, never when it’s on the screen, but we don’t care about that), and when it’s behind us we use Maximize to force at least the X or Y to be 1. That means that whenever it’s off the screen or behind us, the magnitude of newPos (after the if statement) will be larger than 1.

 

If you just throw in a check like that AFTER the if statement that’s already in there, you could just swap the icon there.

 

on March 17, 2018

Thank you very much .

on April 4, 2018

Thank you very much for your code, I owe you a few hours. Working for me on a 2D ortographic game.

In the other hand, in case that anyone is wondering about those few tweaks to make work the off screen icon, here I leave something. This is a quick tweak I made so don’t expect much, at least it’s working.

It does have off screen icon working and also rotation towards the target object (for example, a pointing arrow if the target is off screen).

using UnityEngine;
using UnityEngine.UI;
public class TargetIndicator : MonoBehaviour
{
    private Camera mainCamera;
    private RectTransform m_icon;
    private Image m_iconImage;
    private Canvas mainCanvas;
    private Vector3 m_cameraOffsetUp;
    private Vector3 m_cameraOffsetRight;
    private Vector3 m_cameraOffsetForward;
    public Sprite m_targetIconOnScreen;
    public Sprite m_targetIconOffScreen;
    [Space]
    [Range(0, 100)]
    public float m_edgeBuffer;
    public Vector3 m_targetIconScale;
    [Space]
    public bool PointTarget = true;
    public bool ShowDebugLines;
    //Indicates if the object is out of the screen
    private bool m_outOfScreen;
    void Start()
    {
        mainCamera = Camera.main;
        mainCanvas = FindObjectOfType<Canvas>();
        Debug.Assert((mainCanvas != null), "There needs to be a Canvas object in the scene for the OTI to display");
        InstainateTargetIcon();
    }
    void Update()
    {
        if (ShowDebugLines)
            DrawDebugLines();
        UpdateTargetIconPosition();
    }
    private void InstainateTargetIcon()
    {
        m_icon = new GameObject().AddComponent<RectTransform>();
        m_icon.transform.SetParent(mainCanvas.transform);
        m_icon.localScale = m_targetIconScale;
        m_icon.name = name + ": OTI icon";
        m_iconImage = m_icon.gameObject.AddComponent<Image>();
        m_iconImage.sprite = m_targetIconOnScreen;
    }
    private void UpdateTargetIconPosition()
    {
        Vector3 newPos = transform.position;
        newPos = mainCamera.WorldToViewportPoint(newPos);
        //Simple check if the target object is out of the screen or inside
        if (newPos.x > 1 || newPos.y > 1 || newPos.x < 0 || newPos.y < 0)
            m_outOfScreen = true;
        else
            m_outOfScreen = false;
        if (newPos.z < 0)
        {
            newPos.x = 1f - newPos.x;
            newPos.y = 1f - newPos.y;
            newPos.z = 0;
            newPos = Vector3Maxamize(newPos);
        }
        newPos = mainCamera.ViewportToScreenPoint(newPos);
        newPos.x = Mathf.Clamp(newPos.x, m_edgeBuffer, Screen.width - m_edgeBuffer);
        newPos.y = Mathf.Clamp(newPos.y, m_edgeBuffer, Screen.height - m_edgeBuffer);
        m_icon.transform.position = newPos;
        //Operations if the object is out of the screen
        if (m_outOfScreen)
        {
            //Show the target off screen icon
            m_iconImage.sprite = m_targetIconOffScreen;
            if (PointTarget)
            {
                //Rotate the sprite towards the target object
                var targetPosLocal = mainCamera.transform.InverseTransformPoint(transform.position);
                var targetAngle = -Mathf.Atan2(targetPosLocal.x, targetPosLocal.y) * Mathf.Rad2Deg - 90;
                //Apply rotation
                m_icon.transform.eulerAngles = new Vector3(0, 0, targetAngle);
            }
            
        }
        else
        {
            //Reset rotation to zero and swap the sprite to the "on screen" one
            m_icon.transform.eulerAngles = new Vector3(0, 0, 0);
            m_iconImage.sprite = m_targetIconOnScreen;
        }
        
    }
    public void DrawDebugLines()
    {
        Vector3 directionFromCamera = transform.position - mainCamera.transform.position;
        Vector3 cameraForwad = mainCamera.transform.forward;
        Vector3 cameraRight = mainCamera.transform.right;
        Vector3 cameraUp = mainCamera.transform.up;
        cameraForwad *= Vector3.Dot(cameraForwad, directionFromCamera);
        cameraRight *= Vector3.Dot(cameraRight, directionFromCamera);
        cameraUp *= Vector3.Dot(cameraUp, directionFromCamera);
        Debug.DrawRay(mainCamera.transform.position, directionFromCamera, Color.magenta);
        Vector3 forwardPlaneCenter = mainCamera.transform.position + cameraForwad;
        Debug.DrawLine(mainCamera.transform.position, forwardPlaneCenter, Color.blue);
        Debug.DrawLine(forwardPlaneCenter, forwardPlaneCenter + cameraUp, Color.green);
        Debug.DrawLine(forwardPlaneCenter, forwardPlaneCenter + cameraRight, Color.red);
    }
    public Vector3 Vector3Maxamize(Vector3 vector)
    {
        Vector3 returnVector = vector;
        float max = 0;
        max = vector.x > max ? vector.x : max;
        max = vector.y > max ? vector.y : max;
        max = vector.z > max ? vector.z : max;
        returnVector /= max;
        return returnVector;
    }
}
Show more replies
  • Liked by
Reply
Cancel