- Home /
Finding closest object to player
I'm working on a cover system for a stealth game. In general it's working fairly well except for one thing. When the player goes into cover, I have a script (below) that finds the closest cover object (walls, pedestals, etc.). The problem is, with long walls, the transform of the cover object is sometimes farther away than other cover pieces so the detection determines that other cover pieces are closer which is, less than helpful. If anyone has any thoughts on how to efficiently find the closest object based on it's colliders or vertices or bounds or something else I'm not thinking of, that'd be awesome. Thanks!
void FindNear()
{
GameObject[] gos = GameObject.FindGameObjectsWithTag("Cover");
Vector3 position = transform.position;
float coverDist = coverDistance;
foreach (GameObject go in gos)
{
Vector3 diff = go.transform.position /*Find Closest point on go*/ - position;
float curDistance = diff.sqrMagnitude;
if (curDistance < coverDist)
{
closest = go;
coverDist = curDistance;
}
}
}
Answer by fafase · Oct 29, 2015 at 12:20 PM
Instead of using the models use some cover points. For instance, instead of defining your wall as a cover, place two empty objects at both ends of the wall.
The problem with that as I see it, is either I have the same problem but ins$$anonymous$$d of having the problem at the ends of the wall with the transform in the middle, I'd have the problem in the middle with the objects on the end, or I would need an unnecessarily large amount of empty game objects to make sure the detection was always accurate, slowing the process down a lot. I appreciate the answer, but, in my case, I don't think it'll work.
A large amount is not unnecessary. Those are empty so they don't take much. You could even have a script that collect their position and remove them so you are left with a list of Vector3.
P |x3
|
------------------------------------------------------
x1 $$anonymous$$ x2
See above, $$anonymous$$ is the center, you use x1 or x2 position and you get x1 being the closest to P while your original system would have given x3 as closest. Your call.
A huge amount might not be necessary but I would still need a lot to be perfectly accurate, which, being a s$$anonymous$$lth game, is one of my core objectives. Even on the arcade-y style tile based world I'm working in, there would be areas where it would be possible to be closer to an object on a wall you don't want to be on than an object on a wall you do want to be on until you get to a point of the empty objects being about a quarter of the player size (rough estimation) apart from each other. In addition, the way I currently work the movement while in cover would need complete revamping if I used this method, which, while it may be a good idea in the long run because it's not exactly the best method, I'm currently not super inclined to do if I can help it. I'll think on how I could change it to work using this method though. I have not decided on a solution as of yet though.
I also suggest you stick with this solution and not OverlapSphere, because:
OverlapSphere only checks against the bounding volumes
OverlapSphere doesn't give you point of collision. to abtain that you need to do raycasting
special cover points objects give you more control over covering, e.g. you can put a script with additional parameters on a point object, which can be different for 2 corners of the same wall.
you decouple your level's visuals from gameplay logic, which, also, give you more flexibility
There is no point of collision in this case. One is checking for distance. fafase hit the nail on the head with ClosestPointOnBounds so you can find the closest point to the player, or any object.
The only reason I would personally not use empty objects is that it simply comes across as a total ballache. Thats the only reason. If I can avoid 100 unnecessary editor operations Id be a happy man :P For me, procedural wins every time.
Answer by meat5000 · Oct 29, 2015 at 09:09 PM
Simply use OverlapSphere to obtain all the colliders (use layermask!) and iterate the list for the closest one based on other.transform.position - transform.position
Edit: @fafase's suggestion of ClosetPointToBounds makes this solution work well.
Here's a test class I threw together. Player and Terrain (8/9) are exluded from the OverlapSphere. Press G to fire off the test. It should spit out the closest object at the end.
There is a fair amount of calculation which increases as more objects are involved, rather dramatically. To improve on calculation it doesnt compare all objects together but just against the current closest object. To make this work I specified a Max Search Distance which acts as the threshold for the 'lastLength' variable to work against. You can also put this into OverlapSphere's radius to limit the objects which are entered in to the array, but I haven't bothered. Its pretty crude but it seems to work.
using UnityEngine;
using System.Collections;
//OverlapSphere Test
public class OSTest : MonoBehaviour {
float maxSearchDistance = 50.0f;
Collider thisOne;
LayerMask lm = ~(1<<8 | 1<<9); //Player and Terrain
void Update ()
{
Collider[] myColArray;
float lastLength = maxSearchDistance;
bool flagSet = false;
if(Input.GetKeyDown(KeyCode.G))
{
myColArray = Physics.OverlapSphere(transform.position, 50.0f, lm);
for(int i = 0; i < myColArray.Length; i++)
{
Vector3 temp = myColArray[i].ClosestPointOnBounds(transform.position);
Vector3 tempLength = temp - transform.position;
float sqrLength = tempLength.sqrMagnitude;
if(sqrLength < lastLength*lastLength)
{
thisOne = myColArray[i];
lastLength = tempLength.magnitude;
flagSet = true;
}
}
if(flagSet)
{
Debug.Log(thisOne.gameObject.name);
}
}
}
}
The Enlongated cube is successfully detected despite the origin being clearly much further away than the other objects.
If this solution works for you make sure to give fafase a +1 on his ClosestBounds comment below this answer.
Shoot! beat me to it! But, overlap sphere's are your friend.
How is that fixing the origin issue? The problem was that if you have a long wall, the position is in the middle while the closest part may be one of the end. This is just a more simple way to get the same result isn't it?
@fafase Answered on mobile last night. I may have missed something. Bit tricky to negotiate UA on a 4" screen :P I'd say your method would work the most reliably.
But then, OverlapSphere detects bounding volumes, so its just worth trying it.
$$anonymous$$y guess is that using OverlapSphere will optimise the search, since it will return fewer item to iterate through.
On the other hand, there is an overhead since the physics engine cannot just figure out what is around it.
But my main concern is that the collider array will provide the position of the colliders and not the closest part of the collider. So I would think, it is not really solving the initial problem.
It is possible to use ClosestPointOnBounds on the collider array though which will just add up to the process.
Well, if I remember correctly overlapsphere is an array, and when it finds a collider that will add it to the array. $$anonymous$$eaning that when you reach the first (nearest) collider it will have an index of [0] this is the collider you would want to use?? Could be wrong, but pretty sure that's how it works.
This doesn't seem like a solution to me - its just a faster way to hit the same problem.
$$anonymous$$odified that. Now its a solution (and it works).
But now how do you find the hiding point? I mean this is surely giving the closest point to the player, but looking at your pic, if the cylinder is the enemy, you want to go behind the wall, but your method will return the corner that is facing the enemy.
In any level design, you would define the enemy to come from a specific part of the map, allowing the designer to place safe points. I still think the hassle of hundreds empty objects is worth it.
Or you have one on each side and you check if safe (with linecasting) before placing as closest. Then you get closest and safest.
It may be wise to come up with some editor script to store them in JSON for instance, and have some Load, Save editor buttons. Then you can load them in the scene to visualize them and save them if you apply modifications.
Also, the JSON would save only the positions, so it is faster and smaller than all the Transforms. At runtime, you just run the array of positions through the method.
Answer by wibble82 · Oct 30, 2015 at 11:29 AM
Hi there
There are varying approaches to this that balance accuracy vs cost.
First up, using OverlapSphere is a good optimisation - it helps find all the objects you actually care about, rather than having to check the entire world. For example, if the player can only 'find cover' in objects < 1m away, you want to start with a list of objects within 1m!
Once you have them, the 'hard core' approach would be to check every vertex of the mesh, but that's a little crazy. My gut instinct would be:
iterate over all game objects with mesh renderers you find
transform your test point (i.e. the player transform) into local space of the renderer
clamp it into within the bounds of the mesh
transform it back into world space
This little trick will give you the closest bound between your object and the local bounding box of the mesh. Assuming your objects are fairly boxy, testing the distance between the player and this point will be a good approximation.
Note that if you're happy to drive off colliders instead of the meshes themselves, you could use Collider.ClosestPointOnBounds instead of my clamping process. Either way, the idea is to find the closest point on the target object to the player, then test the distance to it.
Also, use the debug renderer to draw the closest points you find, just in case its not working and you can't understand why!
Would it be possible for you to break that down a little more? As much as I like to think I am a decent programmer, I am still fairly new to Unity, C#, and program$$anonymous$$g in general, and am not quite getting what you are saying. I have not decided on a solution as of yet though.
Your answer
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
How to check for an empty array? 1 Answer
Game lagging while looking for closest Enemy 2 Answers
Getcomponent error? 1 Answer