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?

Just
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.
Andrei, pretty clever, I’ll see what I can create from this and report back. Thanks a lot.
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:
We take the following steps:
- 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.
- With this, we calculate the angle alpha, which we need later on.
- 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.
- 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).
- 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:
This is the code i wrote:
// 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)
GameObject cam = Camera.main.gameObject;
GameObject planet = GameObject.Find("Planet");
Vector3 camPlanetAxis = (planet.transform.position - cam.gameObject.transform.position);
var camDistance = camPlanetAxis.magnitude;
float radius = (planet.transform.localScale.x / 2);
float alpha = Mathf.Asin(radius / camDistance); // the angle between the cam-to-planet line and cam to intersection line (tangent).
// a line from the enemy position that lies orthogonally on the cam-planet vector intersects at:
GameObject fu = GameObject.Find("Enemy(Clone)");
Vector3 pointOnCamPlanetAxisWhereEnemyAxisCrossesOrthogonally = Vector3.Project((fu.transform.position - cam.transform.position), camPlanetAxis); // result is relative to cam position
// this is the distance from the cam-planet axis that the indicator must be placed at to appear on the horizon
float horizonHeight = Mathf.Tan(alpha) * pointOnCamPlanetAxisWhereEnemyAxisCrossesOrthogonally.magnitude;
// define the vector from the cam-planet axis to the enemy and extend it so it reaches the sayHello point
Vector3 enemyAxisOriginInWorldCoordinates = pointOnCamPlanetAxisWhereEnemyAxisCrossesOrthogonally + cam.transform.position;
Vector3 orthogonalEnemyAxis = fu.transform.position - enemyAxisOriginInWorldCoordinates;
Vector3 sayHello = orthogonalEnemyAxis.normalized * horizonHeight + enemyAxisOriginInWorldCoordinates;
// now comes the easy part, project this onto the camera
Vector3 posOnScreen = Camera.main.WorldToViewportPoint(sayHello);
1 Like
No problem, Im happy you found a working solution:)