I ended up using something from both methods. The following code snippets should help illustrate the method I undertook:
class TerrainType {
public String displayName;
public String regionName;
public int movementCost;
/* additional properties omitted */
/* constructors omitted */
}
This structure holds the relevant information on a terrain type including movement cost and other gameplay related stats (I've omitted the rest for simplicity), the display name of the terrain type to show if inspected, and the name of the TextureRegion
to pull from the TextureAtlas
that my renderer is holding so kindly for me.
class GameMapSystem extends EntityProcessingSystem {
@Mapper private ComponentMapper<MapPosition> pm;
@Mapper private ComponentMapper<SolidObject> som;
private ListMultimap<MapPosition, Entity> entityByLocation;
private int[][] map;
private int width, height;
private Array<TerrainType> terrainTypes;
/**
* Accepts an Array of TerrainType objects and an 2d integer array with
* values corresponding to indices into the array for the correct type.
*
* In my case, these values are gleaned by reading a level description
* file, but any source should be fine.
*/
public GameMapSystem(Array<TerrainType> terrainTypes, int[][] map) {
super(Aspect.getForAll(MapPosition.class));
this.terrainTypes = terrainTypes;
this.map = map;
this.width = map.length;
this.height = map[0].length;
this.entityByLocation = ArrayListMultimap.create();
}
public boolean isOccupied(int x, int y) {
List<Entity> entities = entityByLocation(new MapPosition(x, y));
for(Entity e : entities) {
if(som.has(e)) {
return true;
}
}
return false;
}
@Override
protected void inserted(Entity e) {
this.entityByLocation.put(pm.get(e), e);
}
@Override
protected void removed(Entity e) {
this.entityByLocation.remove(pm.get(e), e);
}
/* additional EntityProcessingSystem overrides omitted */
}
This EntityProcessingSystem
is then attached to my world in passive mode. There shouldn't be any real reason to actually do any processing for my world within this system, what I really wanted was to be able to listen to inserted
and removed
events to put entities into the map. A manager would have been overkill in this situation just because it would have told me about EVERY entity being inserted or removed, and I only cared about the map related ones (or more specifically the map related ones with position). I then have some separate path finding logic that consumes additional (not seen here) methods to guide the AI around simply by requesting this passive system from the world object.
For completeness, the MapPosition
class is shown below as well. Of importance is the inclusion of equals()
and hashcode()
to aid in the use of MapPosition
as a key in a collection.
public class MapPosition extends Component
{
public int x, y;
public MapPosition(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object other) {
if(!(other instanceof MapPosition)) {
return false;
}
MapPosition pos = (MapPosition)other;
return (pos.x == this.x && pos.y == this.y);
}
@Override
public int hashCode() {
int hash = 7;
hash = 59 * hash + this.x;
hash = 59 * hash + this.y;
return hash;
}
}
I will likely be trying to find a more convenient data structure than using the guava Multimap
eventually, but it works for right now and I feel comfortable moving on to flesh out the rest of the public API for theses classes. If this answer does help anyone else, do keep in mind that the performance of the ArrayListMultimap
for this implementation has not undergone rigorous testing!