I want to create a “smart” targeting system for a third person shooter style game. The basic concept is this; an invisible line is drawn in front of the player game object for 5 units. Then the line finds the nearest game object with a certain tag within 5 units of any point on the line. So it’s calculating the distance to the closest point on the line. What would be the best way to do this?
To find the distance to a line, there is lots of help out there. There’s even a wikipedia page!
However, following your restricted distance, you need to be able to clamp the reach of that range. To do this, you can use a dot product to find where along the ray the nearest point is (or whether it is behind/beyond the ray).
Below is a nifty class that I have in all of my projects. Given a point, and a line (start and end), it returns the minimum distance to that line. If it goes beyond the line length, it evaluates distance not to the projected line, but instead to the end point.
One solution could be to loop through all objects, comparing their distance to the line, keeping track of the object with the lowest result. Unfortunately, if you have lots of objects, this could be grossly inefficient. The real problem here is reducing the amount of objects that need to be assessed.
I don’t have the perfect answer to that part of the problem, but one approach could be to use Physics.SphereCastAll(), which could work well, especially if you don’t want to be targeting objects extremely far away from their ray. In your case, you could limit the radius to 5, and length of the cast to 5 as well. If you know all the objects will be on particular layers, using the layerMask parameter will help optimize. From these results you can then ignore the ones with incorrect tags, and check DistanceToLine for each, to find the closest.
Remember this is only comparing origins of the objects. Aiming at the edge of a huge object may will prioritize the little guy who’s origin is closer to the ray. If this is really an issue, you could theoretically do multiple SphereCastAll’s, with an increasing radius, stopping after finding anything, and then only using DistanceToLine if there are multiple results.
public static class DistanceToLine
{
public static float Evaluate(Vector3 point, Vector3 start, Vector3 end)
{
float lineProgress;
Vector3 closest;
return Evaluate(point, start, end, out lineProgress, out closest);
}
public static float Evaluate(Vector3 point, Vector3 start, Vector3 end, out float lineProgress)
{
Vector3 closest;
return Evaluate(point, start, end, out lineProgress, out closest);
}
public static float Evaluate(Vector3 point, Vector3 start, Vector3 end, out float lineProgress, out Vector3 closest)
{
Vector3 offset = end - start;
float lengthSquared = offset.sqrMagnitude;
Vector3 fromStart = point - start;
lineProgress = Vector3.Dot(offset, fromStart) / lengthSquared;
if (lineProgress < 0f)
{
closest = start;
}
else if (lineProgress > 1f)
{
closest = end;
}
else
{
closest = start + offset * lineProgress;
}
return (closest - point).magnitude;
}
}
You can shoot a raycast to get the distance from the origin of the ray to the hit point.
RaycastHit hit;
float range=5;
public Camera _myCamera;
void Update () {
if(Input.GetKeyDown(KeyCode.Mouse0)){
if (Physics.Raycast(_myCamera.transform.position, _myCamera.transform.forward, out hit, range)){
if(hit.collider.tag == "TagNameHere"){
print(hit.distance);
}
}
}
}