In Java (Swing), say I've got a 2D game where I have various types of entities on the screen, such as a player, bad guys, powerups, etc. When the player moves across the screen, in order to do efficient checking of what is in the immediate vicinity of the player, I would think I'd want indexed access to the things that are near the character based on their position.
For example, if player 'P' steps onto element 'E' in the following example...
| | | | | |
| | | |P| |
| | |E| | |
| | | | | |
... would be to do something like:
if(player.getPosition().x == entity.getPosition().x &&
entity.getPosition.y == thing.getPosition().y)
{
//do something
}
And thats fine, but that implies that the entities hold their positions, and therefor if I had MANY entities on the screen I would have to loop through all possible entities available and check each ones position against the player position. This seems really inefficient especially if you start getting tons of entities.
So, I would suspect I'd want some sort of map like
Map<Point, Entity> map = new HashMap<Point, Entity>();
And store my point information there, so that I could access these entities in constant time. The only problem with that approach is that, if I want to move an entity to a different point on the screen, I'd have to search through the values of the HashMap for the entity I want to move (inefficient since I dont know its Point position ahead of time), and then once I've found it remove it from the HashMap, and re-insert it with the new position information.
Any suggestions or advice on what sort of data structure / storage format I ought to be using here in order to have efficient access to Entities based on their position, as well as Position's based on the Entity?
Space partitioning could be effective.
You can either do fixed divisions, such as a uniform grid, or a weighted grid if you except there to be hot spots on your map where things gets unusually clustered. For each occupied partition, create a container of all entities it contains.
Insertion is constant time. Removal is a practically infinitesimal fraction of n (your total number of entities) assuming n is very large and you've set up your partitions efficiently. Proximity looksups share the same runtime as removals. Still technically O(n) however small the fraction, though. This would become apparent if a partition ever become unusually crowded.
To get a list of all entities within x units of an entity, you must first get a list of all partitions spanned by a 2x wide square square centered on your entity. If you use a grid partition, this is fairly trivial. Next, you loop through all entities in those partitions and return each one with a distance of x or less to the entity in question.
A more complicated method of partitioning is to dynamically partition space in a tree, ensuring that no partition can contain more than a given number of entities. If a partition becomes full, you divide it along the spacial mean and create two two space partitions such that each one holds half of the original partition's entities. This makes insertions and lookups have logarithmic runtime since you can no longer discover the desired partitions in constant time, but removals become constant time given the capped partition populations.
If there is a one-to-one mapping between Entity
and Point
, and you want to be able to map in both directions, then a bimap is the most natural data structure.
Guava has a BiMap<K,V>
implementation, which supports a BiMap<V,K> inverse()
view operation. It's only a view, so it's not costly at all.
Alternatively you can manage your own Map<Entity,Point>
and Map<Point,Entity>
, but that'd be reinventing the wheel.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With