Search Unity

Show Enemy Indicator on Horizon

Discussion in 'Scripting' started by Linke Seitentasche, Feb 14, 2016.

  1. Linke Seitentasche

    Linke Seitentasche

    Joined:
    Feb 6, 2016
    Posts:
    4
    So I have this little game where I walk across a planet and shoot things (of course).

    The red triangles on the side are off-screen enemy indicators, showing me enemies that are near my back. I used Camera.WorldToViewportPoint to help me determine the location of the enemies in 2D screen space.

    Now I'd like to place some indicators right over the horizon, to show me that there are enemies in front of me that I can't see now, because of the curvature of the planet.

    I simply can't figure out a way how to do this. Maybe create a plane that runs from the camera over the sphere tangentially, then draw a ray from the enemy in upwards direction (relative to the player) and see where it hits the plane.

    Any ideas on this?

    Capture.JPG
     
  2. AndreiKubyshkin

    AndreiKubyshkin

    Joined:
    Nov 14, 2013
    Posts:
    213
    Just png.png quick thoughts.

    You have camera position, planet's center position, target position.

    You can compose a plane from this tree points. Than build a line that lies in this plane and is ortogonal to vector to target (where vectorToTarget = target position - camera position)

    Then you'll find intersections of this line with your sphere, there should be two points. The closest one is the one you need, this is a world-space position of your indicator, just project it to the camera space and place your ui indicator there.
     
  3. Linke Seitentasche

    Linke Seitentasche

    Joined:
    Feb 6, 2016
    Posts:
    4
    Andrei, pretty clever, I'll see what I can create from this and report back. Thanks a lot.
     
  4. Linke Seitentasche

    Linke Seitentasche

    Joined:
    Feb 6, 2016
    Posts:
    4
    Andrei, unfortunately your solution did not take into account the distance of the camera from the planet. With varying distance of the camera, the horizon also changes. It got me thinking however, and from it I got the solution:

    If we take all the rays to every possible point on the horizon, we get a cone. That cone surface represents the space where an item becomes visible on the horizon. Visually speaking, this is a cone that "wraps" or "sits" on the planet and has the camera on its tip.

    The interesting part about this is that the cone has rotational symmetry, so no matter where the enemy is located, we can draw a 2D representation of the scene as in the picture below:

    horizon.jpg

    We take the following steps:
    1. Find the tangent on the circle that runs through the camera. You can derive this with trigonometry, because the camera, the planet center and the tangent intersection form a right-angled triangle. But don't ask me how, since I have no clue about trigonometry.
    2. With this, we calculate the angle alpha, which we need later on.
    3. We draw a line from the enemy down onto the cam-planet axis in orthogonal manner. We need then to elongate this axis upwards until it reaches the cone surface, as indicated by the point sayhello.
    4. We calculate how much we need to elongate this vector until it reaches the horizon cone. We do this using linear algebra. We know the distance from camera point to the enemy orthogonal line on the cam-planet axis, and angle alpha acts as a slope (again, trigonometry).
    5. Map the sayhello point to the camera viewport.
    This way we know where our horizon is regardless of the position and rotation of the camera.

    Works like a charm:

    horizon2.JPG

    This is the code i wrote:

    Code (CSharp):
    1. // Find the tangent intersection point on the planet in a fake 2D space, where the planet is at (0,0) and the camera is aligned left to the planet (-camDistance, 0)
    2. GameObject cam = Camera.main.gameObject;
    3. GameObject planet = GameObject.Find("Planet");
    4. Vector3 camPlanetAxis =  (planet.transform.position - cam.gameObject.transform.position);
    5. var camDistance = camPlanetAxis.magnitude;
    6.      
    7. float radius = (planet.transform.localScale.x / 2);
    8. float alpha = Mathf.Asin(radius / camDistance); // the angle between the cam-to-planet line and cam to intersection line (tangent).
    9.  
    10. // a line from the enemy position that lies orthogonally on the cam-planet vector intersects at:
    11. GameObject fu = GameObject.Find("Enemy(Clone)");
    12. Vector3 pointOnCamPlanetAxisWhereEnemyAxisCrossesOrthogonally  = Vector3.Project((fu.transform.position - cam.transform.position), camPlanetAxis); // result is relative to cam position
    13.  
    14. // this is the distance from the cam-planet axis that the indicator must be placed at to appear on the horizon
    15. float horizonHeight = Mathf.Tan(alpha) * pointOnCamPlanetAxisWhereEnemyAxisCrossesOrthogonally.magnitude;
    16.  
    17. // define the vector from the cam-planet axis to the enemy and extend it so it reaches the sayHello point
    18. Vector3 enemyAxisOriginInWorldCoordinates = pointOnCamPlanetAxisWhereEnemyAxisCrossesOrthogonally + cam.transform.position;
    19. Vector3 orthogonalEnemyAxis = fu.transform.position - enemyAxisOriginInWorldCoordinates;
    20. Vector3 sayHello = orthogonalEnemyAxis.normalized * horizonHeight + enemyAxisOriginInWorldCoordinates;
    21.  
    22. // now comes the easy part, project this onto the camera
    23. Vector3 posOnScreen = Camera.main.WorldToViewportPoint(sayHello);
     
    Last edited: Feb 20, 2016
    ThermalFusion likes this.
  5. AndreiKubyshkin

    AndreiKubyshkin

    Joined:
    Nov 14, 2013
    Posts:
    213
    No problem, Im happy you found a working solution:)