- Home /
Off-Camera view Object tracker
Hello everybody!
So I have this problem: basically I want to have a kind of tracker that tells the direction of a certain object e.g. an enemy when it goes off the camera view. The situation would be something like in the image below:
The player being the red square, the object that I want to track the blue ellipse and the tracker the yellow arrow. We are talking of a top-down 2D game. I was thinking to give the arrow the angle between player and object to have the direction; my specific problem, however, is that I do not know where to start from in order to bound the arrow on the screen borders and only when the targeted object is out of sight. I imagine it should be something tweaking something with the camera? Is the arrow supposed to be an UI element or something else?
Thanks to whoever can give me even a hint! ;)
I don't know the answer, but I think this post can help you with the "is it not seen by the camera" part: https://answers.unity.com/questions/8003/how-can-i-know-if-a-gameobject-is-seen-by-a-partic.html
Answer by Ymrasu · Feb 28, 2019 at 12:53 AM
Getting the line between the player and your target is easy. But then to put the arrow on that line requires some college algebra: the slope-intercept formula. With that formula you can pick an X pos (the edge of the screen) to get the Y pos of the line at that point.
public GameObject player;
public GameObject target;
public Image arrowPointerUI;
public void Update()
{
float x = 0f;
float y = 0f;
Vector2 A = Camera.main.WorldToScreenPoint(player.transform.position);
Vector2 B = Camera.main.WorldToScreenPoint(target.transform.position);
// slope
var m = (B.y - A.y) / (B.x - A.x);
// y-intercept
var b = A.y + (-m * A.x);
// x and y formulas reference
// y = (m * x) + b;
// x = (y - b) / m;
Vector2 offset = arrowPointerUI.rectTransform.sizeDelta / 2;
Rect scrRect = new Rect(offset, new Vector2(Screen.width - offset.x, Screen.height - offset.y));
// clamp target x pos to screen to help find y
x = Mathf.Clamp(B.x, scrRect.xMin, scrRect.xMax);
y = (m * x) + b;
// if y is off screen clamp it, then find a better x
if (y < scrRect.yMin || y > scrRect.yMax) {
y = Mathf.Clamp(y, scrRect.yMin, scrRect.yMax);
x = (y - b) / m;
}
// arrow position
Vector2 C = new Vector2(x, y);
// this checks if target is on screen or not
if (Vector2.Distance(B, C) > offset.x) {
// target is far from arrow, so it is off screen
// show arrow and position it
arrowPointerUI.enabled = true;
arrowPointerUI.transform.position = C;
// rotate arrow to point from player to target
float angle = Mathf.Atan2(B.y - A.y, B.x - A.x) * Mathf.Rad2Deg;
arrowPointerUI.transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
} else {
// target is close to arrow, so it is on screen
// hide arrow
arrowPointerUI.enabled = false;
}
}
private void OnDrawGizmos()
{
// Draw line between player and target for debugging
Vector2 A = player.transform.position;
Vector2 B = target.transform.position;
Gizmos.DrawLine(A, B);
}
This assumes a top-down view with the arrow being an UI Image and that it is square. This works fine as long as the player remains inside the scrRect bounds that was set up. If the player leaves those bounds, you can set up a check to hide the arrow.
It works almost perfectly wonderful! The almost is because it seems that the arrow does not rotate following the precise angle described by the difference between player and target and because a half of the arrow itself falls out of screen. You solved the main issue however so great anyway! ;)
If the half the arrow is leaving the screen you can adjust the offset to be bigger. I had originally made to be just half the size of the rectTransport of the UI element. This also assumes the position of the UI element is the center of it. If you position the arrow from say the top-left corner, you should subtract the offset from the position of the arrow.