Offscreen enemy indicator....

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…

369797--12816--$indicators_478.png

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

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.

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

Thinking about that some more, it’s a little less obvious in implementation :wink: 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 :wink:

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.

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).

I appologize for my dumbness on this subject what I’ve done this

Vector3 pos=Camera.main.WorldToScreenPoint (enemyTarget.position);
	if (pos.z<0) {
           calculate the x,y on the screen bounderies
        }

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

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.)

Behind/in-front makes no difference except in the z-component of the returned vector.

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

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

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:

			Vector3 pos=Camera.main.WorldToScreenPoint (enemyTarget.position);

				float halfy=Screen.height/2f;
				float halfx=Screen.width/2f;				
				float slope=(pos.y-halfy)/(pos.x-halfx); // slope with the center of the screen
				
				if (pos.y>Screen.height) { // to the top
					pos.y=Screen.height;
					pos.x=(Screen.height-halfy)/slope;					
				} else if (pos.y < 0) { // to the bottom
					pos.y=7f; // height of the texture
					pos.x= (-halfy/slope);	
				} else if (pos.x > Screen.width) { // to the right
					pos.x=Screen.width-7f;
					pos.y=slope*pos.x + halfy;
				} else { // just right behind me or my left
					pos.x=0;
				
				}
			
				rect=new Rect(pos.x,Screen.height-pos.y,7,7);
				GUI.DrawTexture(rect, targetArrow, ScaleMode.StretchToFill, true, 0);

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.

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.)

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.

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:

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:

Vector2 origin = p1;
Vector2 direction = p2 - p1;
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:

bool IntersectLine(
    Vector2 origin,
    Vector2 direction,
    Vector2 normal,
    float distance,
    ref Vector2 outP)
{
	float denom = Vector2.Dot(direction, normal);
	if (Mathf.Abs(denom) > .01) {
		float numer = distance - Vector2.Dot(origin, normal);
		float t = numer / denom;
		if (t >= 0f) {
			outP = origin + t * direction;
			return true;
		}
	}
	return false;
}

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.)

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?

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 :wink:

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 )

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

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.

431733–14964–$PaulygonsOffScreenIndicator.unitypackage (22.5 KB)

2 Likes

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.

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!

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.