How can I make sprite stay in viewport and point towards the game object it is attached to?

Hi all,

I am making top down 3d shooter game for android in C#, Unity version is 2017.1. I have waves of enemies coming. Up to 50 enemies maximum at a time, no more. What I want to achieve is to have a little marker for each enemy when it is not in camera viewport. What I have now is a simple red dot sprite that is only showing if enemy is not visible(I am using: OnBecameVisible() and OnBecameInvisible()). That works fine. What I can’t get right is sprites movement to be more precise. Below is what I have found on Google and adjusted it myself.

Vector3 enemyPosition;
    public float speed;
   
    void Update()    
    {
        float cameraY = Camera.main.transform.position.y - 14;
        if(cameraY <= 16.5f)
        {
            cameraY = 16.5f;
        }

        enemyPosition = new Vector3(transform.parent.position.x, cameraY,transform.parent.position.z);
        float step = speed * Time.deltaTime;
        transform.position = Vector3.MoveTowards(transform.position,enemyPosition, step);
        
        Vector3 pos = Camera.main.WorldToViewportPoint(transform.position);
        pos.x = Mathf.Clamp01(pos.x);
        pos.y = Mathf.Clamp01(pos.y);
        transform.position = Camera.main.ViewportToWorldPoint(pos);
    }

At the moment sprite will mostly stay in the corners of the screen, sometimes jumping quickly from one corner to the adjacent one before settling. When I am almost in line with enemy than sprite will show precise location of the enemy on this axis. If I go lets say 1 unit down than sprite will go 2 units up from the line where enemy is standing, it seems to grow exponentially. This seems to be the cause of the jumpiness of sprite as well.
I am not to sure if Clamp01() is the best way to make those sprites behave better and point better to the parent enemy (red dot sprite is a child of the enemy).

Hope I have managed to explain it ok, if you need more info or pics shout.

Bear in mind that I am fairly new to Unity and horrible at math. I have spent last 2 days trying to get those dots behave. If you have better solution, proposition or links to where i can get myself educated on the subject please help. This is pretty much last coding wise problem I have in this otherwise cool project. CHEERS!

LoOnar Forge

I wouldn’t make the tracking objects be children of the objects you’re tracking.
I also think there may be a problem with setting transform.position = the viewport point of the object.
Viewport space goes from (0,0) to (1,1)

Here’s my crack at this problem, from the top (I’m assuming that you want your tracking sprites to be placed in world space, not screen space).

  • Spawn or make visible your “Tracker”
    object

  • Create a function for your tracker:
    “SetTracking(object)” or something
    like that. It is now tracking that
    object.

  • Create a function: FindClosestPositionInView(object, camera), this would return a position that’s just within the camera’s view in world-space, taking into account. Use it to place the tracking sprite.

  • Use the resulting point to place your tracker

  • Rotate the tracker to point towards the object (code below for finding the angle)

Here’s some pseudo-code:

Vector3 FindClosestPositionInView(object, camera)
{
  Vector3 toObject = object.transform.position - camera.transform.position;
  
   Vector3[] frustumCorners;
   camera.CalculateFrustumCorners(camera.rect, Zdepth, camera.stereoScopicEye, out frustumCorners); 

  toObject.Normalize();
 Vector3 intersectPoint;
 
  for each (line that makes up the frustum)
  {
     test to see if it intersects with the toObject line we made.
     Find the intersection point.
  } 
  If there's multiple intersection points, use the one that's closest to the object.

   compensate intersect point inwards, using the toObject vector and the object width, so the tracker is completely inside the view.
   
   return intersectPoint;
}

Since this game is basically 2D, it’s pretty easy to make the object point towards another.
using a vector to the object, you can get the angle of that object. Here’s a function I use in my 2D top-down game, to get the 2D angle of a vector. This is like: 0 is straight up, 90 is to the left, 270 faces right.
This is an extension method for Vector2.

	public static float GetAngle(this Vector2 vec, bool bNormalize = true)
	{
		Vector2 vector = vec;
		if(bNormalize)
			vector.Normalize();
		//Find out what quadrant it is in
		//is it in the to-left quadrant?
		bool right=false, left=false, bottom=false, top=false;

		if(vector.x > 0)
			right = true;
		else if(vector.x < 0)
			left = true;
		if(vector.y > 0)
			top = true;
		else if(vector.y < 0)
			bottom = true;

		if(top && left)
			return (float)Math.Atan((double)Mathf.Abs(vector.x)/vector.y) * Mathf.Rad2Deg;
		else if(bottom && left)
			return (float)(Math.Atan(Mathf.Abs(vector.y)/Mathf.Abs(vector.x)) * Mathf.Rad2Deg) + 90;
		else if(bottom && right)
			return (float)(Math.Atan(vector.x/Mathf.Abs(vector.y)) * Mathf.Rad2Deg) + 180;
		else if(top && right)
			return (float)(Math.Atan(vector.y/vector.x) * Mathf.Rad2Deg) + 270;
		else //vector resides in between 2 quadrants
		{
			if(left)
				return 90;
			else if(bottom)
				return 180;
			else if(right)
				return 270;
			else
				return 0;
		}
			
	}	

After you have the angle, you can do:

trans.localRotation = Quaternion.Euler(0, 0, angle);