One more thought, based on the fact you need to do distance checks to work stuff out, a good way around that is to use a simplified 2D representation of the world to see if something is close to the player. For instance you could check if the enemy was within 40 and 80 world units of the player (in x and z) by dividing the world into 40 world unit squares and seeing if the player is in the current cell or any adjacent one. This doesn’t require square roots and only requires that each item writes and removes itself from the grid as it moves. This is also a good technique for lots of things that involve proximity and the like. For instance I use it to keep 100s of characters apart without raycasts or built in collision detection.
I’ve built a simple(ish) class to support it.
#SupportClasses.cs
public class DefaultDictionary< TK,TR > : Dictionary< TK,TR>
{
public new virtual TR this[TK index]
{
get
{
if (ContainsKey(index))
{
return base[index];
}
return default(TR);
}
set
{
base[index] = value;
}
}
public T Get< T >(TK index) where T : TR
{
return (T)this[index];
}
}
public class Index< TK,TR > : DefaultDictionary< TK,TR > where TR : class, new()
{
public event Action< TK, TR, TR> Setting = delegate { };
public event Action< TK, TR> Getting = delegate { };
public override TR this[TK index]
{
get
{
if (ContainsKey(index))
{
return base[index];
}
var ret = new TR();
if (ret is INeedParent)
{
(ret as INeedParent).SetParent(this, index);
}
base[index] = ret;
Getting(index, ret);
return ret;
}
set
{
if (Setting != null)
{
TR current = null;
if (base.ContainsKey(index))
{
current = base[index];
}
Setting(index, current, value);
}
base[index] = value;
}
}
}
#CollisionGrid.cs
using UnityEngine;
using System.Linq;
using System.Collections.Generic;
using System;
public class GridCellPosition
{
public int z;
public int x;
static readonly Index< int, Index< int, GridCellPosition>> cache = new Index< int, Index< int, GridCellPosition>>();
public static GridCellPosition Get(int x, int z)
{
GridCellPosition cell;
if(!cache[x].TryGetValue(z, out cell))
{
cell = new GridCellPosition
{
x = x,
z = z
};
cache[x][z] = cell;
}
return cell;
}
public static implicit operator GridCellPosition(Vector3 position)
{
return Get(Mathf.FloorToInt(position.x),
Mathf.FloorToInt(position.z));
}
public GridCellPosition Offset(int offsetX, int offsetZ)
{
return Get(x + offsetX, z + offsetZ );
}
public static GridCellPosition operator / (GridCellPosition pos, int value)
{
return Get(pos.x / value, pos.z / value );
}
public static GridCellPosition operator * (GridCellPosition pos, int value)
{
return Get(pos.x * value, pos.z * value );
}
}
public class CollisionGrid< T>
{
public int size = 5;
public virtual bool Add(GridCellPosition position, T item) {return false; }
public virtual IEnumerable< T> Get(GridCellPosition position) { return null; }
public virtual IEnumerable< T> Get(GridCellPosition position, float width, float height) {return null;}
public IEnumerable< T> this[GridCellPosition position]
{
get
{
return Get(position);
}
}
public virtual bool CheckCell(GridCellPosition position) { return false; }
readonly List< T> _emptyList = new List<T>();
public IEnumerable< T> this[Component position]
{
get
{
return position == null ? _emptyList : Get(position.transform.position);
}
}
public virtual bool Add(T item)
{
if (item == null)
throw new Exception("Trying to add null to a grid");
if (item is Transform)
{
return Add((item as Transform).position, item);
}
if(item is Component)
{
return Add((item as Component).transform.position, item);
}
if(item is GameObject)
{
return Add((item as GameObject).transform.position, item);
}
throw(new Exception("Cannot get a position from " + item.GetType().FullName));
}
public virtual void Clear() {}
public virtual void Remove(T item) {}
public virtual void Remove(GridCellPosition position, T item) {}
}
public class SparseCollisionGrid< T> : CollisionGrid< T> {
readonly Index< GridCellPosition, HashSet<T>> _grid = new Index< GridCellPosition, HashSet< T>>();
public override bool Add(GridCellPosition position, T item)
{
position /= size;
var result = InternalCheck(position, item);
_grid[position].Add(item);
return result;
}
public override void Remove(GridCellPosition position, T item)
{
position /= size;
_grid[position].Remove(item);
}
public override void Remove(T item)
{
if (item is Transform)
{
Remove((item as Transform).position, item);
}
if(item is Component)
{
Remove((item as Component).transform.position, item);
}
else if(item is GameObject)
{
Remove((item as GameObject).transform.position, item);
}
else throw(new Exception("Cannot get a position from " + item.GetType().FullName));
}
public override IEnumerable< T> Get(GridCellPosition position)
{
position /= size;
//var list = Factory.New< List< T>>();
var list = new List< T>();
list.AddRange(_grid[position.Offset(0,0)]);
list.AddRange(_grid[position.Offset(0,1)]);
list.AddRange(_grid[position.Offset(1,0)]);
list.AddRange(_grid[position.Offset(1,1)]);
list.AddRange(_grid[position.Offset(-1,0)]);
list.AddRange(_grid[position.Offset(0,-1)]);
list.AddRange(_grid[position.Offset(1,-1)]);
list.AddRange(_grid[position.Offset(-1,1)]);
list.AddRange(_grid[position.Offset(-1,-1)]);
return list;
}
public override IEnumerable< T> Get (GridCellPosition position, float width, float height)
{
width = Mathf.FloorToInt(width/size)+1;
height = Mathf.FloorToInt(height/size)+1;
position /= size;
//var list = Factory.New< List< T>>();
var list = new List< T>();
for(var x = 0; x < width + 2; x++)
{
for(var z = 0; z < height+2; z++)
{
var result = _grid[position.Offset(x, -z)];
if(result!=null)
list.AddRange(result);
}
}
return list;
}
public override bool CheckCell(GridCellPosition position)
{
return _grid[position].Count > 0;
}
public override void Clear()
{
_grid.Clear();
}
}
#Using it
You use it by defining a collision grid and giving it a size
public static CollisionGrid< AnyScriptOrComponent> collisionGrid = new SparseCollisionGrid< AnyScriptOrComponent>();
...
collisionGrid.size = 40;
Then thing that wants to be detected adds and removes itself as it moves (removing first really)
void Update()
{
collisionGrid.Remove(this);
...
transform.position += Vector3.right * 2;
...
collisionGrid.Add(this);
}
Then to get all of the items within adjacent cells (or the current cell)
foreach(var item in collisionGrid[this])
{
if(item == this) continue;
//Do something with the neighbour
}
If you were just looking for the player then you could not add the enemies at all and just have the player in there, then its a simple check
if(collision.CheckCell(transform.position))
{
//Player is close
}
else
{
//Not so much
}