Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Offscreen enemy indicator....

Discussion in 'Scripting' started by spacefrog, Aug 19, 2010.

  1. spacefrog

    spacefrog

    Joined:
    Jun 14, 2009
    Posts:
    734
    I'm working on a topdown view game currently with several enemy units moving around on the map. Only a small portion of the map is displayed in a top down view.Now i want to implement small icons on the screenborder for every enemy outside the current view. Those icons should slide along the screenborder and always indicate the direction the enemy in question is located. While i've all things setup, i'm currently stuck totally in the math involved to update the icons position in the Update() function of the icon..
    The icon knows which "target" transform it has to track so it would be easy as cake, would'nt be me having a hard time currently, making myself unable to concentrate on such problems....
    I made a sketch that illustrates the problem

    So how to calc the position of the indicators most effectively?
    Would be really thankfull if someone could help me out, or at least give me some starters...
     

    Attached Files:

  2. jedy

    jedy

    Joined:
    Aug 1, 2010
    Posts:
    579
    Well simple solution would be:

    -create an invisible box
    -make it the appropriate size ( the box will be the current map view - the box should be the exact same size as the current map view )
    -script the box, so it would move with the map view
    -raycast from every enemy to the box ( or vise version )
    -create the pointers on the position of raycasting collision and rotate them so they face the appropriate enemy
    -for doing this you should use quite a bit of math to convert from world position to map position
     
  3. laurie

    laurie

    Joined:
    Aug 31, 2009
    Posts:
    638
    Is this for a mini-map view, or the main screen? Either way, the solution would involve

    • calculating world position of screen center (use a raycast from the camera to screen; see ScreenPointToRay or ViewportPointToRay
    • calculating the vector from that position to the enemy position
    • clipping that vector to the screen bounds
    • drawing your indicator at the resulting position

    That approach would also give you the correct angle of rotation to make the indicators actually point at the enemy, rather than just 90 degrees to screen border, if you wanted that.
     
  4. creat327

    creat327

    Joined:
    Mar 19, 2009
    Posts:
    1,756
    Hi

    I was actually just gonna ask the same question so this is perfect.

    I know how to do all your points on the list but how do you clip the vector to only the screen size? In my case it's the main screen camera trying to track where my enemies are located.

    What I'm doing is raycasting from the center of the screen to the target but I don't know how to clip that vector distance to my borders.

    Regards,
    Joaquin
     
  5. laurie

    laurie

    Joined:
    Aug 31, 2009
    Posts:
    638
    Thinking about that some more, it's a little less obvious in implementation ;-) As long as Camera.WorldToScreenPoint() doesn't clip, though, you can just convert everything back from world space to screen space and use the formula for a line (y = mx + c) to get the right position. See the Coordinate geometry here if you need a math refresher ;-)
     
  6. creat327

    creat327

    Joined:
    Mar 19, 2009
    Posts:
    1,756
    laurie
    i think if the object is behind you, you are going to get some funny results with that. I don't know how the worldtoscreen method will do.
     
  7. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    You don't want to use the slope-intercept form for this, as it can't represent vertical lines and will most likely lead to numerical issues when dealing with nearly-vertical lines.

    Instead, use the parametric form (P(t) = O+tD) or the general form (Ax + By + C = 0).
     
  8. creat327

    creat327

    Joined:
    Mar 19, 2009
    Posts:
    1,756
    I appologize for my dumbness on this subject what I've done this

    Code (csharp):
    1.  
    2. Vector3 pos=Camera.main.WorldToScreenPoint (enemyTarget.position);
    3.     if (pos.z<0) {
    4.            calculate the x,y on the screen bounderies
    5.         }
    6.  
    Since I have pos.x and pos.y I imagine it shouldn't be hard to extrapolate/intrapolate to the screen border but I just don't know how.

    Regards
     
  9. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    If you want to avoid doing the math manually, you could use Plane.Raycast() for this. Set up four planes corresponding to the edges of the screen, create a ray from the center of the screen through the projected point, intersect the ray with each of the four planes, and take the closest intersection; this is the point you're interested in.

    (The planes are easy to construct; the normals are the + and - cardinal x and y basis vectors, and the distances are 0, 0, Screen.width, and Screen.height.)
     
  10. laurie

    laurie

    Joined:
    Aug 31, 2009
    Posts:
    638
    Behind/in-front makes no difference except in the z-component of the returned vector.

    Quite right :) I was being lazy by referencing Wikipeida instead of enumerating all the options :oops:

    You don't want that test, unless you really only want markers for enemies in front of the player.
     
  11. creat327

    creat327

    Joined:
    Mar 19, 2009
    Posts:
    1,756
    Well, I'm trying to do the math but not working very well. I just don't know how to use that formula you mentioned so I used the regular slope formula that I learned long ago. I wrote this:

    Code (csharp):
    1.  
    2.  
    3.             Vector3 pos=Camera.main.WorldToScreenPoint (enemyTarget.position);
    4.  
    5.                 float halfy=Screen.height/2f;
    6.                 float halfx=Screen.width/2f;               
    7.                 float slope=(pos.y-halfy)/(pos.x-halfx); // slope with the center of the screen
    8.                
    9.                 if (pos.y>Screen.height) { // to the top
    10.                     pos.y=Screen.height;
    11.                     pos.x=(Screen.height-halfy)/slope;                 
    12.                 } else if (pos.y < 0) { // to the bottom
    13.                     pos.y=7f; // height of the texture
    14.                     pos.x= (-halfy/slope); 
    15.                 } else if (pos.x > Screen.width) { // to the right
    16.                     pos.x=Screen.width-7f;
    17.                     pos.y=slope*pos.x + halfy;
    18.                 } else { // just right behind me or my left
    19.                     pos.x=0;
    20.                
    21.                 }
    22.            
    23.                 rect=new Rect(pos.x,Screen.height-pos.y,7,7);
    24.                 GUI.DrawTexture(rect, targetArrow, ScaleMode.StretchToFill, true, 0);
    25.  
    26.  

    It kinda works. I see the texture on the left, right, top and bottom but it has some serious issues:

    1) It will show even when the object is in front of the camera. Using pos.z doesn't seem to do the trick since the arrow seems to dissapear some times randomly.

    2) When the object is right behind you, the positions x y seems to be inversed. I guess Camera.main.WorldToScreenPoint has some mirror effect.


    Anyone got this working and willing to share code?
    I can do the 4 planes thing but I'm sure there is a way to do it with simple math without creating 4 planes and as efficient as possible since this runs on iphone.
     
  12. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    Did you read my earlier post? To reiterate, you don't want to use the slope-intercept form.

    I doubt the plane-based approach would be overly expensive, but if you want to do it manually, try Googling 'line intersection'; there are plenty of good references online explaining how to intersect two lines in 2-d. (If you're really worried about efficiency, you can optimize the code even further due to the axis-alignment of the lines representing the screen edges.)
     
  13. creat327

    creat327

    Joined:
    Mar 19, 2009
    Posts:
    1,756
    Jesse, first line on my post:
    "I just don't know how to use that formula you mentioned so I used the regular slope formula that I learned long ago."

    I googled it and try to understand it, but it was as clear as quantum physics to me. I just didn't find a clear example on how to use it for what I want to do.
     
  14. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    Oh, ok. I didn't realize the 'you' in that sentence was me.

    Here's an example to maybe get you pointed in the right direction. (This'll be off the top of my head, so no guarantee I'll get everything right.)

    So you have the center of the screen, call it p1:

    Code (csharp):
    1. Vector2 p1 = new Vector2(Screen.width * .5f, Screen.height * .5f);
    And you have the projected point of interest, p2. First, convert the line passing through these two points to parametric form, like this:

    Code (csharp):
    1. Vector2 origin = p1;
    2. Vector2 direction = p2 - p1;
    3. direction /= direction.magnitude;
    Note that if you haven't already removed from consideration entities that are visible onscreen or are behind the player, you'll need to check the magnitude of 'direction' against an epsilon (small value, such as .01) first and bail if it's below that threshold. (This will mean that the entity is more or less directly ahead of or behind the player, in which case the direction for the indicator can't be uniquely determined.)

    Now, you need a function to intersect your ray with a 2-d hyperplane (i.e. a line). Again, no guarantee I'll get this right, but here goes:

    Code (csharp):
    1. bool IntersectLine(
    2.     Vector2 origin,
    3.     Vector2 direction,
    4.     Vector2 normal,
    5.     float distance,
    6.     ref Vector2 outP)
    7. {
    8.     float denom = Vector2.Dot(direction, normal);
    9.     if (Mathf.Abs(denom) > .01) {
    10.         float numer = distance - Vector2.Dot(origin, normal);
    11.         float t = numer / denom;
    12.         if (t >= 0f) {
    13.             outP = origin + t * direction;
    14.             return true;
    15.         }
    16.     }
    17.     return false;
    18. }
    For the derivation of this algorithm, Google 'ray plane intersection'. (In short, you plug the ray equation into the plane equation and solve for t.)

    All that's left is to determine the hyperplane normals and distances for the four edges of the screen. They are:

    left: normal = (1,0), distance = 0
    right: normal = (-1,0), distance = Screen.width
    top: normal = (0,1), distance = 0
    bottom: normal = (0,-1), distance = Screen.height

    (Maybe top and bottom are reversed - I can't remember which way the +y axis points in screen space in Unity.)
     
  15. creat327

    creat327

    Joined:
    Mar 19, 2009
    Posts:
    1,756
    Thanks man.

    It doesn't work though due to the WorldToScreen method not doing exactly what needed.

    I think the best way is to do it in 3d as the other posts suggested but that requires either creating 4 planes on unity and raycast or doing 4 planes mathematically, rotate them to your camera position and then calculate the interception.

    In any case, way too complicated for what I was expecting so unless someone got some insight to share, i'm giving up on this one.

    Did the original poster got it to work?
     
  16. spacefrog

    spacefrog

    Joined:
    Jun 14, 2009
    Posts:
    734
    UPDATE:
    found the reason why it failed on the device!!!
    I had my Globals.Screencenter variable defined&initialized as a static variable - not thinking about the fact that the app does'nt know anything about the screenres prior running on the device. So the global initialized with the values which were valid during startup - when the orientation on the iPhone is set to potrait by some unknown reason. So width and height where swapped. As soon i implemented a constructor for Globals, and initialized the ScreenCenter there everything works - even on Device!!!

    whoa - thanks guys for this great discussion !
    I did'nt realize that - just have seen it today...
    Thanks again - i'm still not able to use my brain at it's full potential ;-)

    The way I got it to work somehow, but strangly it doesn't work on the device (iPhone) but only in the editor?

    Steps used:
    ( Globals.Screencenter is a Vector3 holding the screencenter (width/2 , height/2, 0 )

    Code (csharp):
    1. * create a orthographic camera (i use that to draw UI/Hud elements)
    2. * create a bounding box BBox (Bounds) - representing that orthocam's frustum
    3. * in enemy Update() following happens:
    4.  transfer enemy pos to Screenspace ( WorldToscreenPoint on the 3D Cam which renders the enemy)
    5.  create a ray from orthocam's center (=Vector3.zero)
    6.   to screenpos of enemy
    7.   Do a BBox.IntersectRay( ray , out dist)
    8.   this should give me the distance where the ray hits the BBox
    9.   use ray.GetPoint(dist) to get the point where that happens
    10.  
     
  17. paulygons

    paulygons

    Joined:
    May 6, 2010
    Posts:
    164
    Hi, I tried to solve this without having to understand slopes, hyper-planes, or neuroscience. This package does the trick, if by trick I mean very CPU expensive (I assume ray casting is not cheap, and things got slow with lots of trackable objects and a big scene). It's also extremely limited, it only works as a side-scroller type of implementation with all objects being on the same z-plane. My oh so brilliant solution is 4 planes at the edges of the camera frustum that catches rays shot from the center towards each trackable object. I was shooting for this (the tiny widgets on the edges of the screen). So far, not even close...

    If anyone has found a more versatile, efficient method i would LOVE to see it. The prior posts here seem to understand a potential method that is a lot more intelligent but I can't seem to get it working that way.
    And all twenty three thousands bytes of this package are released under the WTFPL.
     

    Attached Files:

    votagus and erebel55 like this.
  18. tylo

    tylo

    Joined:
    Dec 7, 2009
    Posts:
    154
    Wow, thanks for releasing this. It's really quite good.

    I'm not sure what you mean that it is "not even close". Have you played Eve Online during large fleet battles? Everyone shuts off these indicators because they cause too much lag.

    I'm getting brilliant FPS with a "sensible" number of objects in the scene using your code.

    As for getting it to work with objects that are very far ahead or behind you, just ignore that. Do all your Raycasts as if they were right beside you.
     
  19. paulygons

    paulygons

    Joined:
    May 6, 2010
    Posts:
    164
    Tylo, thanx for the kudos! By "not even close" I meant that the Eve indicator works in 3 dimensions. As stated, this has severe limitations due to the way it's determining indicator location based on a 4 sided bounding box surrounding the edge of the screen. I was a tiny bit proud of getting the bounding box to scale itself to whatever screen size you used.

    Oh, I think your solution for the 2D limitation just dawned on me. It seems like it would be possible to "collapse" the other objects into the bounding box's plane. In other words, say an object is off screen, but far ahead/behind, maybe I could eliminate it's distance but keep the offset, thereby allowing me to test as usual. Sort of like the way an orthographic camera works. Thanks for piping up, I don't know that I would have thought of that myself.

    Edit: and yes, of course I binge on Eve every so often!
     
  20. dongio

    dongio

    Joined:
    Sep 28, 2010
    Posts:
    16
    I made it!!!

    created custom mash witch is similar to camera cone atached mash to custom collider and cast rays from enemies I'm polishing now the code to add camera Field of view so it will calculate mesh properly.
    other than that it works aspect ratio change, angle change, far clipping is working.

    it's JS script needs to be added to camera. as soon as i finish Field of View Code ill upload it.
     
  21. dongio

    dongio

    Joined:
    Sep 28, 2010
    Posts:
    16
    var newVertices : Array;
    var newTriangles : Array;

    function Start () {
    //print(Screen.height/2);
    //print(Screen.currentResolution.width);
    // float.parse
    var ratio: float = (Screen.width*1.0)/(Screen.height*1.0);
    var boxSize: float = 170.0/20 * camera.fieldOfView;

    newVertices = new Array();
    newVertices.Add(Vector3.Reflect(new Vector3(boxSize*ratio,boxSize,1000.0), Vector3.zero));
    newVertices.Add(Vector3.Reflect(new Vector3(boxSize*ratio,-boxSize,1000.0), Vector3.zero));
    newVertices.Add(Vector3.Reflect(new Vector3(-boxSize*ratio,-boxSize,1000.0), Vector3.zero));
    newVertices.Add(Vector3.Reflect(new Vector3(-boxSize*ratio,boxSize,1000.0), Vector3.zero));

    // Vector3.Reflect (originalObject.position, Vector3.right);
    //newVertices.Add(Camera.main.transform.position);
    newVertices.Add(new Vector3(0.0,0.0,0.0));


    gameObject.AddComponent(MeshCollider);
    // var tr Triangulator = new Triangulator(newVertices);

    var mesh : Mesh = new Mesh ();


    //GetComponent(MeshFilter).mesh = mesh;
    mesh.vertices = newVertices;

    var uvs = new Vector2[mesh.vertices.length];

    for (var i=0; i<uvs.Length; i++) {
    uvs = Vector2(mesh.vertices.x, mesh.vertices.z);
    }
    mesh.uv = uvs;
    //2,1,0, 0,3,2,
    mesh.triangles = [0,1,4, 1,2,4, 2,3,4, 3,0,4];
    mesh.RecalculateNormals();
    transform.GetComponent(MeshCollider).sharedMesh = mesh;
    transform.GetComponent(MeshCollider).isTrigger = true;
    }
     
  22. dongio

    dongio

    Joined:
    Sep 28, 2010
    Posts:
    16
    here it is i you have any questions just PM me I'll Upload example in couple days.

    PS: newTriangles:Array is not needed, i just forgot to clean it up.
     
  23. paulygons

    paulygons

    Joined:
    May 6, 2010
    Posts:
    164
    I wrestled with this some more after Tylo's comments, but still couldn't figure out a way to make it work on objects that are not on the plane of interest (concerning my 2d implementation). I just tried your code and it's VERY cool. I will try again armed with that. Thanx for posting it!
     
  24. PrimeDerektive

    PrimeDerektive

    Joined:
    Dec 13, 2009
    Posts:
    3,090
    Have you tried Eric's Object Label script? His method works great for me.
     
  25. paulygons

    paulygons

    Joined:
    May 6, 2010
    Posts:
    164
    Huh. OK. Yeah that stings a bit. That works perfectly and I've missed it through every wiki dredge I've done. THANKS lagend411!!
     
  26. ForceX

    ForceX

    Joined:
    Jun 22, 2010
    Posts:
    1,102
    The Object Lable script works great except for when the object gets behind you. Then everything is reversed. up is down and left is right. also it would be nice if when the object is behind you that it would stay stuck to the screen edges.
     
  27. creat327

    creat327

    Joined:
    Mar 19, 2009
    Posts:
    1,756
    Hi guys

    I actually managed to make it work on 3d just now. I decided to post it as a paid job and people were taking weeks to have it ready. At the end I began doing it myself yesterday night and by this morning it was finished (right when I got the code from the paid job :(
    Anyway, my code is ultra fast, 10 lines of vector math. It just took me a while to figure the 3d line clipping algorithm.

    As a demo I decided to target a hangar and draw a gizmo to it. You can see the square is actually in 3d and that it moves at the edges of the screen when the object is not in view. Video: http://www.youtube.com/watch?v=DztjuiJloMw
     
  28. ForceX

    ForceX

    Joined:
    Jun 22, 2010
    Posts:
    1,102
    @creat326: This is perfect. Do you still have plans to make this available on the asset store or through your self?
     
  29. marjan

    marjan

    Joined:
    Jun 6, 2009
    Posts:
    563
    Hey guys,
    in case anybody searched for something like this, here is my solution. And others. First, let me tell you there is at least one solution on the asset store. The good points about that one are: A: Quite cheap B: Does what it says C:Might give you some scripting hints.
    And the bad sides are: A: Every indicator is rendered by OnGui, this 2 Drawcalls each, thus slow. Superslow. B : Requires you to attach a script to any gameobject you want to track. Unfortunatly that script isn´t to optimized, has like 500 lines of code and every gameobject is doing over and over again the same calculations and look ups. I mean, not even transforms are cached, jeez...
    On the plus side, its a little bit customizable, like you can select to have indicators only left or only right and fade them in and out.
    But why should you have arrows point to the right or left, if the indicator is anyways right or left...

    Ok. So, what i wanted was just one shared sprite, appearing at the border of the screen. Same thing for all enemies or whatever you wanna track. Then, i dont want to attach a lot of scripts to a lot of gameobjects. And, i dont want to have more then one drawcall for all of this.
    Thus, you can forget about OnGui. I used NGui and setup a UIPanel with one testSprite. Thats the indicator. Maybe its important to have the GUI being centered by archor.

    Other than that i have a javascript with some necessare inputs:

    Code (csharp):
    1. public var camGUI : Camera; // this is the camera used by NGUI
    2. private var cam : Camera;  // this is the gamecamera
    3. private var halfScreenWidth : float = Screen.width/2; // just precomputed here to avoid doing this in loops
    4. private var halfScreenHeight : float = Screen.height/2;
    5. public var testIndicator2D : Transform; // this is the sprite in the NGUI Setup serving as an Indicator
    6.  
    Furthermore i have an internal array storing the enemies i want to track. In my case this is inside a class instance : currentWave.EnemysInstantiated but thats just a : Transform[]

    Please note that i just track one enemy in this script. But the idea works fine. The indicator moves at he right position on all sides of the screen.

    What is still missing here:
    - Create a pool of indicators in the NGui SetUp for multiple targets
    - Hide ore fade out the indicators, once the GameObject is in Camera Frustrum
    - change the loop to support multiple targets.

    But, thats the basics in just a handfull of codelines. (Well, you can alwas optimize more...)


    Code (csharp):
    1. public function enemyRadar() {
    2.  // Get the ray going through the center of the screen
    3.    
    4.    
    5.     for(var allenemies : Transform in currentWave.EnemysInstantiated) {
    6.         if (allenemies != null) {
    7.             var renderers : Renderer[] = allenemies.GetComponentsInChildren.<Renderer>();
    8.            
    9.             if (!renderers[0].IsVisibleFrom(cam)) {
    10.                 // This Enemy is out of Frustrum, so we want an indicator point to its direction it.
    11.                 var worldToViewportPoint : Vector3 = cam.WorldToViewportPoint (allenemies.position);
    12.                 // returns coming from upper left
    13.                 //worldToViewportPoint = (-0.1, 0.5, 14.8), viewportToScreenPoint =(-66.1, 361.3, 14.8)
    14.                                        
    15.                 var screenPosClamped : Vector3  = new Vector3(Mathf.Clamp(worldToViewportPoint.x, 0.0f, 1.0f), Mathf.Clamp(worldToViewportPoint.y, 0.0f, 1.0f), 0);
    16.                
    17.                 var v3 : Vector3 = new Vector3(screenPosClamped.x * Screen.width -halfScreenWidth, screenPosClamped.y * Screen.height -halfScreenHeight, 0);
    18.                                                                        
    19.                 //Debug.Log("enemy " + allenemies.name + " out of view at worldToViewportPoint = " + worldToViewportPoint + ", v3 =" + v3);
    20.                 testIndicator2D.localPosition = v3;
    21.                 break;
    22.             }
    23.         }
    24.     }
    25. }
     
    erebel55 likes this.
  30. paulygons

    paulygons

    Joined:
    May 6, 2010
    Posts:
    164
    Thanks for posting this marjan! I'll let you know if I make any further progress on this topic.
     
  31. Ensutee

    Ensutee

    Joined:
    Apr 11, 2013
    Posts:
    10
  32. haan

    haan

    Joined:
    Oct 20, 2014
    Posts:
    9
    Hi all.

    I want to show small icons on circle border. Which points to enemies. in other words I want to narrow the vision area to a circle(for example : aircraft shooting range) not screen(which is rectangular). Circle's radius and center point are given.
    Any ideas how to implement this?
    Thank you in advance.
     
  33. WiedemannD

    WiedemannD

    Joined:
    Mar 4, 2015
    Posts:
    19
    If somebody else is also having issues with strange values coming from camera.WorldToScreenPoint() when the object is far behind the camera, see my following post https://forum.unity3d.com/threads/camera-worldtoscreenpoint-bug.85311/#post-2844282

    In short:
    To calculate the angle and the on screen position of indicators I used digijin's approach


    But instead of using camera.WorldToScreenPoint() I used the to the camera locally transformed position of the object.
     
  34. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    646
    There was no simple but good answer anywhere on Forum, Answers or even youtube.. almost everyone was using some kind of slope formular but I came up with a very simple solution, which is also pretty fast as it only uses some vector-math.
    Enjoy:

    Code (CSharp):
    1. void LateUpdate()
    2. {
    3.     // get the position on screen, in screen coordinates
    4.     Vector3 targetPosition = _testPosition.position;
    5.     Vector3 screenPos = _playerCam.WorldToScreenPoint(targetPosition);
    6.     if (Mathf.Approximately(screenPos.z, 0))
    7.     {
    8.         return;
    9.     }
    10.  
    11.     // save half screen resulution because we will need it often
    12.     Vector3 halfScreen = new Vector3(Screen.width, Screen.height) / 2;
    13.  
    14.     // we don't want the Z-Value in our center-vector because it would
    15.     // cause problems when normalizing it
    16.     Vector3 screenPosNoZ = screenPos;
    17.     screenPosNoZ.z = 0;
    18.     // get the vector from the center of the screen to the
    19.     // calculated screen position
    20.     Vector3 screenCenterPos = screenPosNoZ - halfScreen;
    21.  
    22.     // we have to invert the vector when we are looking away from the target
    23.     // the vector is just projected on the view-plane, think looking in a mirror
    24.     if (screenPos.z < 0)
    25.     {
    26.         screenCenterPos *= -1;
    27.     }
    28.  
    29.     // debug check, if the ray is pointing in the wanted direction
    30.     // can only be seen with gizmos enabled, in scene view (3D Mode only)
    31.     //Debug.DrawRay(halfScreen, screenCenterPos.normalized * 100000, Color.red);
    32.  
    33.     // check if the target is on screen
    34.     if (screenPos.z < 0 || screenPos.x > Screen.width || screenPos.x < 0 ||
    35.         screenPos.y > Screen.height || screenPos.y < 0)
    36.     {
    37.         // if you have a arrow on your symbol, pointing in the
    38.         // direction, enable it here:
    39.         _testInstance.rotator.gameObject.SetActive(true);
    40.  
    41.         // rotate it to point towards the target position
    42.         _testInstance.rotator.rotation =
    43.             Quaternion.FromToRotation(Vector3.up, screenCenterPos);
    44.  
    45.         // normalized ScreenCenterPosition
    46.         Vector3 norSCP = screenCenterPos.normalized;
    47.  
    48.         // avoid dividing by zero
    49.         if (norSCP.x == 0)
    50.         {
    51.             norSCP.x = 0.01f;
    52.         }
    53.         if (norSCP.y == 0)
    54.         {
    55.             norSCP.y = 0.01f;
    56.         }
    57.  
    58.         // stretch the normalized screenCenterPosition so that X is at the edge
    59.         Vector3 xScreenCP = norSCP * (halfScreen.x / Mathf.Abs(norSCP.x));
    60.         // stretch the normalized screenCenterPosition so that Y is at the edge
    61.         Vector3 yScreenCP = norSCP * (halfScreen.y / Mathf.Abs(norSCP.y));
    62.  
    63.         // compare the streched vectors in length and use the smaller one
    64.         if (xScreenCP.sqrMagnitude < yScreenCP.sqrMagnitude)
    65.         {
    66.             screenPos = halfScreen + xScreenCP;
    67.         }
    68.         else
    69.         {
    70.             screenPos = halfScreen + yScreenCP;
    71.         }
    72.     }
    73.     else
    74.     {
    75.         // if you have a arrow on your symbol, pointing in the
    76.         // direction, disable it here:
    77.         _testInstance.rotator.gameObject.SetActive(false);
    78.     }
    79.  
    80.     // clamp the result, so we can always see the full marker/tracker image
    81.     float margin = 70;
    82.  
    83.     screenPos.z = 0;
    84.  
    85.     screenPos.x = Mathf.Clamp(screenPos.x, margin, Screen.width - margin);
    86.     screenPos.y = Mathf.Clamp(screenPos.y, margin, Screen.height - margin);
    87.  
    88.     // set the transform position
    89.     _testInstanceT.position = screenPos;
    90. }
     
  35. tratteo

    tratteo

    Joined:
    Dec 17, 2018
    Posts:
    2
    Thanks man i was literally searching for this, you saved me a lot of geometric headhache!! ;)
     
  36. Hi_ImTemmy

    Hi_ImTemmy

    Joined:
    Jul 8, 2015
    Posts:
    174
    Blessings on this thread.
     
  37. nxtboyIII

    nxtboyIII

    Joined:
    Jun 4, 2015
    Posts:
    280
    Thank you sir you are a saint!
     
  38. paulygons

    paulygons

    Joined:
    May 6, 2010
    Posts:
    164
    I'm so glad this thread is still yielding results! Thanks for sharing Hannibal_Leo.
     
  39. AlexStrook

    AlexStrook

    Joined:
    Oct 19, 2013
    Posts:
    31
    Thanks a lot Hannibal_Leo, this code works perfectly
     
  40. Wekthor

    Wekthor

    Joined:
    Apr 30, 2011
    Posts:
    164
    @Hannibal_Leo Amazing, thank you.

    Here is slightly adjusted version with setting opacity for the indicator based on distance. It's probably not best solution, but seems to work.


    Code (CSharp):
    1. void SetIndicator(Transform obj)
    2.     {
    3.         // get the position on screen, in screen coordinates
    4.         Vector3 objPosition = obj.position;
    5.         Vector3 objScreenPos = cam.WorldToScreenPoint(objPosition);
    6.         if (Mathf.Approximately(objScreenPos.z, 0))
    7.         {
    8.             return;
    9.         }
    10.    
    11.         // save half screen resulution because we will need it often
    12.         Vector3 halfScreen = new Vector3(Screen.width, Screen.height) / 2;
    13.    
    14.         // we don't want the Z-Value in our center-vector because it would
    15.         // cause problems when normalizing it
    16.         Vector3 objScreenPosNoZ = objScreenPos;
    17.         objScreenPosNoZ.z = 0;
    18.         // get the vector from the center of the screen to the
    19.         // calculated screen position
    20.         Vector3 screenCenterPos = objScreenPosNoZ - halfScreen;
    21.    
    22.         // we have to invert the vector when we are looking away from the target
    23.         // the vector is just projected on the view-plane, think looking in a mirror
    24.         if (objScreenPos.z < 0)
    25.         {
    26.             screenCenterPos *= -1;
    27.         }
    28.    
    29.         // debug check, if the ray is pointing in the wanted direction
    30.         // can only be seen with gizmos enabled, in scene view (3D Mode only)
    31.         //Debug.DrawRay(halfScreen, screenCenterPos.normalized * 100000, Color.red);    
    32.  
    33.         //0-1 is on screen, < 0.0 && > 1.0 is off screen. Over 10 it flips signs and behaves weird, show arrows and adjust opacity only in range to 10
    34.         //Remove -0.5 to center the value to 0.0 and multiple it by 2 to make it -1 to 1
    35.         Vector2 distance = new Vector2( ((objScreenPos.x / Screen.width) - 0.5f) * 2f, ( (objScreenPos.y / Screen.height) - 0.5f) * 2f);    
    36.        
    37.    
    38.         // check if the target is off the screen, then show the arrow
    39.         if (objScreenPos.z < 0 || objScreenPos.x > Screen.width || objScreenPos.x < 0 || objScreenPos.y > Screen.height || objScreenPos.y < 0)
    40.         {
    41.             //Use the distance value from x,y that is furthest
    42.             float maxDistance = Mathf.Max(Mathf.Abs(distance.x), Mathf.Abs(distance.y));
    43.             //Enemy is off screen when the value is more than 1.0. Clamp the distance to 10.0f. Adjust opacity of arrow based on the distance
    44.             if(maxDistance > 1.0f) {
    45.                 maxDistance = Mathf.Clamp(maxDistance-1f, 0.0f, 10.0f);
    46.                 float opacityValue = (10f - maxDistance) / 10f;
    47.                 arrowImg.color = new Color(1,0,0,opacityValue);
    48.             } else {
    49.                 arrowImg.color = new Color(1,0,0,1);
    50.             }
    51.  
    52.             // if you have a arrow on your symbol, pointing in the
    53.             // direction, enable it here:
    54.             arrow.SetActive(true);
    55.    
    56.             // rotate it to point towards the target position          
    57.             indicatorParent.rotation = Quaternion.FromToRotation(Vector3.up, screenCenterPos);
    58.    
    59.             // normalized ScreenCenterPosition
    60.             Vector3 norSCP = screenCenterPos.normalized;
    61.    
    62.             // avoid dividing by zero
    63.             if (norSCP.x == 0)
    64.             {
    65.                 norSCP.x = 0.01f;
    66.             }
    67.             if (norSCP.y == 0)
    68.             {
    69.                 norSCP.y = 0.01f;
    70.             }
    71.    
    72.             // stretch the normalized screenCenterPosition so that X is at the edge
    73.             Vector3 xScreenCP = norSCP * (halfScreen.x / Mathf.Abs(norSCP.x));
    74.             // stretch the normalized screenCenterPosition so that Y is at the edge
    75.             Vector3 yScreenCP = norSCP * (halfScreen.y / Mathf.Abs(norSCP.y));
    76.  
    77.            
    78.    
    79.             // compare the streched vectors in length and use the smaller one
    80.             if (xScreenCP.sqrMagnitude < yScreenCP.sqrMagnitude)
    81.             {
    82.                 objScreenPos = halfScreen + xScreenCP;
    83.             }
    84.             else
    85.             {
    86.                 objScreenPos = halfScreen + yScreenCP;
    87.             }
    88.         }
    89.         else
    90.         {
    91.             // if you have a arrow on your symbol, pointing in the
    92.             // direction, disable it here:
    93.             arrow.SetActive(false);
    94.         }
    95.    
    96.         // clamp the result, so we can always see the full marker/tracker image
    97.         float margin = 70;
    98.    
    99.         objScreenPos.z = 0;
    100.    
    101.         objScreenPos.x = Mathf.Clamp(objScreenPos.x, margin, Screen.width - margin);
    102.         objScreenPos.y = Mathf.Clamp(objScreenPos.y, margin, Screen.height - margin);
    103.    
    104.         // set the transform position
    105.         indicatorParent.position = objScreenPos;
    106.     }